18

I am using React Native for my application, and at one point, I noticed that I have to type this.state.bar[this.state.foo][SOME_NUMBER] every time, within my components.

This works perfectly fine, but I want to make my code cleaner by calling a function instead. So, I constructed this:

function txt(n){
    return this.state.bar[this.state.foo][n];
}

However, when I run this in the Expo client, I get the following error:

TypeError: undefined is not an object (evaluating 'this.state.bar')

This error is located at:
    in App...
    ....

Here is my entire code.

import React, 
    { Component } 
from 'react';
import { 
     ... 
} from 'react-native';

export default class App extends React.Component {
    state = {
        foo: 'ABC',
        bar: {
            'ABC': [
                '...',
                '...',
                '...'
            ]
        }
    };
    render() {
        function txt(n){
            return this.state.bar[this.state.foo][n];
        }
        return (
            <View>
                ...  
            </View>
        );
    }
}

I tried placing the text() function outside the App class, but got the same error.

When I placed it outside render() in App, I got an unexpected token error.

When I defined this.state within a constructor(props) and placed text() within the constructor, I got ReferenceError: Can't find variable: text

Mrigank Pawagi
  • 892
  • 1
  • 8
  • 26

2 Answers2

28

Your problem is scoping.

The this that you're trying to access inside the txt() function is pointing to its own this, and not the outer component this.

There are several ways to fix this. here's a few:

Use arrow functions

You can transform txt into an arrow function to use the outer this:

render() {
    const txt = (n) => {
        return this.state.bar[this.state.foo][n];
    }
    return (
        <View>
            ...  
        </View>
    );
}

You can bind the function to use the outer this

render() {
    function _txt(n){
        return this.state.bar[this.state.foo][n];
    }


    const txt = _txt.bind(this);

    return (
        <View>
            ...  
        </View>
    );
}

You can create an additional variable that points to the outer this

render() {
    const self = this;
    function txt(n){
        return self.state.bar[self.state.foo][n];
    }
    return (
        <View>
            ...  
        </View>
    );
}

Other approaches

  • You can move the txt function to outside of the render function and bind it to the component this.
  • You can use an arrow function inside the component class block, which will seem like you've bound it to the component's this.
  • You can pass the state as a parameter to the function

...and I'm sure that there are several other ways to fix this behaviour. You just need to know when you're using the component's this or some other this.

Sergio Moura
  • 4,888
  • 1
  • 21
  • 38
  • Thanks a lot! Worked like a charm! I used the 2nd approach and added `const text = _text.bind(this)` and works! Basically, I knew I had to use `bind()` but I couldn't understand where and how! – Mrigank Pawagi Oct 19 '18 at 14:47
  • 3
    The "problem" with binding it like that is that (or even creating functions inside the render function for that matter) is that you are creating a new function element every time your component renders. This is one of the reasons that's good NOT to create functions inside the render function for very big components. – Sergio Moura Oct 19 '18 at 14:49
  • I see your point - thankfully my function is a small one! Thanks! – Mrigank Pawagi Oct 20 '18 at 13:08
1

A few issues here. First, you need to bind the text function to the class in the constructor. You also need to move the text function out of the render method and add it as a class method (no function in front of function name):

import React,
{ Component }
  from 'react';
import {
...
} from 'react-native';

export default class App extends React.Component {

  constructor () {
    super();
    this.state = {
      foo: 'ABC',
      bar: {
        'ABC': [
          '...',
          '...',
          '...'
        ]
      }
    };
    this.text = this.text.bind(this);
  }

  text (n) {
    return this.state.bar[this.state.foo][n];
  }

  render() {
    return (
      <View>
        ...
      </View>
    );
  }
}
Chase DeAnda
  • 15,963
  • 3
  • 30
  • 41
  • Gives `Can't Find variable: text` – Mrigank Pawagi Oct 19 '18 at 14:44
  • 1
    @MrigankPawagi by using Chase's answer, you need to use `this.text` inside of the `render` function, since the function is not available as a keyword inside the `render` function. You could, on the top of the render function, do `const text = this.text` and use it without the `this` from that point on. – Sergio Moura Oct 19 '18 at 14:46