74

I am following a beginner tutorial from Pluralsight, on form submit a value is passed to addUser component method and I need to push userName to this.state.users but I get error

 App.jsx:14 Uncaught TypeError: Cannot read property 'users' of undefined

Component

import React from 'react'
import User from 'user'
import Form from 'form'

class Component extends React.Component {
    constructor() {
        super()
        this.state = {
            users: null
        }
    }
    // This is triggered on form submit in different component
    addUser(userName) { 
        console.log(userName) // correctly gives String
        console.log(this.state) // this is undefined
        console.log(this.state.users) // this is the error
        // and so this code doesn't work
        /*this.setState({
            users: this.state.users.concat(userName)
        })*/
    }
    render() {
        return (
            <div>
            <Form addUser={this.addUser}/>
            </div>
            )
    }
}

export default Component
Ivan Topić
  • 3,064
  • 6
  • 34
  • 47

9 Answers9

124

When you call {this.addUser} , it gets called, here this is an instance of your class(component), and thus it gives no error to you because addUser method does exist in your class scope, but when you are under addUser method you are using this to update the state which exist in the scope of class(component), but currently you are within the scope of addUser method and so it gives you an error as under addUser Scope you got nothing like state, user etc. So to deal with this problem you need to bind this while you are calling addUser method.So that your method always knows the instance of this.

So the final change in your code will look like this:-

<Form addUser={this.addUser.bind(this)}/>

OR


You can bind this in the constructor,because it is the place when you should intialize things because constructor methods are called first when the components render to the DOM.

So you can do it in this way:-

  constructor(props) {
    super(props);
    this.state = {
        users: null
    }
    this.addUser=this.addUser.bind(this);
}

And now you can call it in normal way as you did before:-

<Form addUser={this.addUser}/>

I hope this will work,And I made it clear to You.

Vinit Raj
  • 1,900
  • 1
  • 15
  • 16
  • 6
    Why constructor and render functions understand 'this' correctly, but addUser has it as undefined? – Loreno Sep 29 '18 at 14:52
  • 2
    @Loreno, functions are object and have a scope for themselves. With bind(this) or function expression (lambda expression), we are defining the scope of the function to be the present class. I hope this would be helpful. – Ajay Jun 06 '19 at 09:42
  • 1
    @Loreno `Component` (the defined component that contains the other HTML elements) is what calls `contstructor` and `render`, but the `Form` element within the component is what is attempting to call `addUser`, however, it doesn't contain a reference to that function. Other people suggested using the code `{ () => this.addUser() }` because this code calls `addUser` within the scope of the `render` function, meaning `this` refers to `Component` and not `Form`. – Dave F Oct 21 '21 at 19:52
16

@Vinit Raj's approaches works perfectly - tho i prefer to use the arrow function syntax like so.

<Form addUser={ () => this.addUser() }/>

Using an anonymous function like this, you don't need to bind it anywhere.

Filtenborg
  • 377
  • 6
  • 13
  • 6
    This will create a new function on every render which has performance implications. Since the function doesn't take any arguments, it would be better to declare it like this: `
    ` and declare add user with an arrow function like `addUser = () => ...`
    – Marc Sloth Eastman Jan 18 '19 at 22:11
  • 3
    The whole "this might cause performance issues" trope is a dead horse. Benchmark it to prove it instead of spreading techno-babble. How can you prove that calling `bind` on `this` is faster than creating a new arrow function? The fact is, it's probably so small that it literally cannot be detected in benchmarks. Any possible deviation between the two is vastly outweighed by the uncertainty in factors such as network latency. Use whichever is more readable, until *it actually becomes a measurable bottleneck* in your code. – dlq Jan 12 '21 at 08:56
5

If you prefer using arrow function do this. The arrow function syntax as like below

addUser = event => { 
    const { value }  = event.target;
    console.log("value", value);
}

To prevent this function being called on every render or re-render you need to

Change

<Form addUser={this.addUser}/>

To

<Form addUser={() => this.addUser()}/>

So that addUser gets called only when event is happened/triggered

Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162
3

I had the same problem but my issue was trying to access this.state before the this.state = { ... } call finished. Was doing something like this this.state = { ...this.function1() } and function1 = () => { a: this.state.b }. Hope this helps someone

Marc Sloth Eastman
  • 693
  • 1
  • 10
  • 19
3

I don't think the other answers explain this very well. Basically the problem is that Javascript's this keyword is insane (MDN very generously says it "behaves a little differently in JavaScript compared to other languages").

The TL;DR is that this is not necessarily the class that the method is defined in. When you use this.addUser in your <Form> element, this is actually the Form object! I'm not sure why React does this - I can't really think why anyone would want that, but it's easy to verify. Just put console.log(this) in addUser and you'll find it is an instance of Form.

Anyway the solution is pretty simple - use an arrow function. Arrow functions make a copy of this at the point where they are defined. So if you put one in your render function, as other people have suggested:

<Form addUser={() => this.addUser()}/>

Then when render() is run, this will refer to the Component object, the arrow function will make a copy of it, and then when it is run it will use that copy. It's basically a shorthand for this code (and this is what people used to do before arrow functions):

    render() {
        const that = this; // Make a copy of this.
        return (
            <div>
            <Form addUser={function() { return that.addUser; }}/>
            </div>
            )
    }

Also, as other people have mentioned, creating a new function every time you call your render function might have performance implications, so it is better to capture this once somewhere else. I think this is a better approach than what other people have suggested:

class Component extends React.Component {
    constructor() {
        super()
        this.state = {
            users: null
        }
    }
    // This is triggered on form submit in different component
    addUser = (userName) => { 
        this.setState({
            users: this.state.users.concat(userName)
        })
    }
    render() {
        return (
            <div>
            <Form addUser={this.addUser}/>
            </div>
            )
    }
}

But this is my first day using React, and I normally try to avoid using languages as completely barking as Javascript, so take all this with a pinch of salt!

Timmmm
  • 88,195
  • 71
  • 364
  • 509
2

In your case by declaring your function as a fat arrow function you add context and remove the requirement to bind to this. This works just the same as the other solutions but makes things a lot simpler to both write and read. Just change...

 addUser(userName) { 
    console.log(userName) // correctly gives String
    console.log(this.state) // this is undefined
    console.log(this.state.users) // this is the error
    // and so this code doesn't work
    /*this.setState({
        users: this.state.users.concat(userName)
    })*/
}

to...

addUser = (userName) => { 
    console.log(userName) // correctly gives String
    console.log(this.state) // this is undefined
    console.log(this.state.users) // this is the error
    // and so this code doesn't work
    /*this.setState({
        users: this.state.users.concat(userName)
    })*/
}

And everything else can stay the same.

0

A good pattern is to bind a method to the class in the constructor function. See https://reactjs.org/docs/handling-events.html

import React from 'react'
import User from 'user'
import Form from 'form'

class Component extends React.Component {
    constructor() {
        super()
        this.state = {
            users: null
        }
  this.addUser = this.addUser.bind(this); 
  //bind functions which need access to "this"v in the constructor here. 

    }
    // This is triggered on form submit in different component
    addUser(userName) { 
        console.log(userName) // correctly gives String
        console.log(this.state) // this is undefined
        console.log(this.state.users) // this is the error
        // and so this code doesn't work
        /*this.setState({
            users: this.state.users.concat(userName)
        })*/
    }
    render() {
        return (
            <div>
            <Form addUser={this.addUser}/>
            </div>
            )
    }
}

export default Component
Greggory Wiley
  • 660
  • 6
  • 16
0

The problem is in this context, so use

<form onSubmit={(event)=>this.addUser(event)}>
   // Inputs
</form>
addUser(event){
   event.preventDefault()
   // Code this.state 
}
Gil
  • 43
  • 3
-2

Simpley use async in function

addUser =async (userName) => { console.log(this.state.users) }

this will works fine

Qamar
  • 1
  • 2