1

I try to map an array and put click event on the array items. I know it's a bit different because of how JavaScript handles functions but I can't make it work. I get the error: Cannot read property 'saveInStorage' of undefined. Can anyone tell me what I'm doing wrong? Thanks in advance! Here is my code:

import React from "react";

const data = require('../data.json');

export default class Gebruikers extends React.Component {

    constructor() {
        super();
        this.state = {
            users: data.users
        };
        this.saveInStorage = this.saveInStorage.bind(this)
    }

    saveInStorage(e){
        console.log("test");
    }

    renderUser(user, i) {
        return(
            <p key={i} onClick={this.saveInStorage(user)}>f</p>
        );  
    }

    render() {
        return (
            <div>
                {
                    this.state.users.map(this.renderUser)
                }
            </div>
        );
    }
}

5 Answers 5

2

this is undefined in renderUser() You need to bind this for renderUser() in your constructor. Also, you are calling saveInStorage() every time the component is rendered, not just onClick, so you'll need to use an arrow function in renderUser

import React from "react";

const data = require('../data.json');

export default class Gebruikers extends React.Component {

  constructor() {
    super();
    this.state = {
        users: data.users
    };
    this.saveInStorage = this.saveInStorage.bind(this);
    this.renderUser = this.renderUser.bind(this);
  }

  saveInStorage(e){
    console.log("test");
  }

  renderUser(user, i) {
    return(
        <p key={i} onClick={() => this.saveInStorage(user)}>
    );  
  }

  render() {
    return (
        <div>
            {
                this.state.users.map(this.renderUser)
            }
        </div>
    );
  }
}

Instead of binding you can also use an arrow function (per mersocarlin's answer). The only reason an arrow function will also work is because "An arrow function does not have its own this; the this value of the enclosing execution context is used" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The enclosing execution in your case is your render, where this is defined.

Sign up to request clarification or add additional context in comments.

1 Comment

This doesn't work either. this.saveInStorage(user) gets invoked when the component is rendered.
1

You need to make two changes to your code which are outlined below.

  1. You are invoking the function when the component is rendered. To fix this update this line to the following

    <p key={i} onClick={() => this.saveInStorage(user)}>
    

This means that the function will only be invoked when you click on the item.

  1. You also need to bind the renderUser in your constructor or else use an arrow function.

    this.renderUser = this.renderUser.bind(this);
    

See working example here.

Comments

1

Your onClick event handler is wrong. Simply change it to:

onClick={() => this.saveInStorage(user)}

Don't forget to also bind renderUser in your constructor. Alternatively, you can choose arrow function approach as they work the same as with bind:

class Gebruikers extends React.Component {
  constructor(props) {
    super(props)
    
    this.state = {
      users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
    }
  }

  saveInStorage = (e) => {
    alert("test")
  }

  renderUser = (user, i) => {
    return(
      <p key={i} onClick={() => this.saveInStorage(user)}>
        {user.name}
      </p>
    )  
  }

  render() {
    return (
      <div>{this.state.users.map(this.renderUser)}</div>
    );
  }
}

ReactDOM.render(
  <Gebruikers />,
  document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

4 Comments

@PaulFitzgerald What are you asking about? The arrow function approach?
Yes, you also need to bind renderUser in the constructor, or else use an arrow function for renderUser. You have only covered saveInStorage in your answer
@PaulFitzgerald about the arrow function approach you can check in React's docs
i know what the arrow function is and how it works. I was pointing out that your answer was not correct. Neither of the two answers that had been upvoted here actually worked.
1

Paul Fitzgeralds answer is the correct one, although I'd like to propose a different way of handling this, without all the binding issues.

import React from "react";

const data = require('../data.json');

export default class Gebruikers extends React.Component {

    constructor() {
        super();
        this.state = {
            users: data.users
        };
    }

    saveInStorage = (e) => {
        console.log("test");
    };

    render() {
        return (
            <div>
                {this.state.users.map((user, i) => {
                    return (<p key={i} onClick={() => this.saveInStorage(user)}>f</p>);
                })}
            </div>
        );
    }
}

With saveInStorage = (e) => {}; you are binding the saveInStorage function to the this context of your class. When invoking saveInStorage you'll always have the (at least I guess so in this case) desired this context.

The renderUser function is basically redundant. If you return one line of JSX, you can easily do this inside your render function. I think it improves readability, since all your JSX is in one function.

4 Comments

This sounds good but I get an error, unexpected token. I can't figure out how to fix it. But it says that one of the closing parentheses after the closing </p> unnecessary is.
Well, how about removing one? You're right there is a syntax error. Corrected the answer.
Yes but then I get: Unterminated regular expression on the closing div tag. I have no idea why that happens.
Oh, I am very sorry. I got confused with the closing brackets and parantheses. Updated the answer.
0

You are not sending the parameters to this.renderUser

this.state.users.map((user, i) => this.renderUser(user, i))

Also your onClick function should be slightly changed. Here's the full code changed:

import React from "react";

const data = require('../data.json');

export default class Gebruikers extends React.Component {

    constructor() {
        super();
        this.state = {
            users: data.users
        };
        this.saveInStorage = this.saveInStorage.bind(this)
    }

    saveInStorage(e){
        console.log("test");
    }

    renderUser(user, i) {
        return(
            <p key={i} onClick={() => this.saveInStorage(user)}>f</p>
        );  
    }

    render() {
        return (
            <div>
                {
                    this.state.users.map((user, i) => this.renderUser(user, i))
                }
            </div>
        );
    }
}

1 Comment

this.state.users.map((user, i) => this.renderUser(user, i)) This line is redundant. this.state.users.map(this.renderUser) is already sufficient enough, since this.renderUser is just the callback function that is getting invoked by the .map function. You could argue that the content of renderUser could be inline in the render function, though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.