13

I'm new to React and JavaScript.

I have a Menu component which renders an animation onClick and then redirects the app to another route, /coffee.

I would like to pass the value which was clicked (selected) to function this.gotoCoffee and update this.state.select, but I don't know how, since I am mapping all items in this.state.coffees in the same onClick event.

How do I do this and update this.state.select to the clicked value?

My code:

class Menus extends Component{
  constructor (props) {
    super(props);
    this.state = { 
        coffees:[],
        select: '',      
        isLoading: false,
        redirect: false
    };
  };
  gotoCoffee = () => {
    this.setState({isLoading:true})
    setTimeout(()=>{
      this.setState({isLoading:false,redirect:true})
    },5000)
  }

  renderCoffee = () => {
    if (this.state.redirect) {
      return (<Redirect to={`/coffee/${this.state.select}`} />)
    }
  }

  render(){
    const data = this.state.coffees;

    return (
      <div>
        <h1 className="title is-1"><font color="#C86428">Menu</font></h1>
        <hr/><br/>
        {data.map(c => 
          <span key={c}>
            <div>
               {this.state.isLoading && <Brewing />}
               {this.renderCoffee()}
              <div onClick={() => this.gotoCoffee()} 
                  <strong><font color="#C86428">{c}</font></strong></div>
            </div>
          </span>)
        }
      </div>
    );
  }
}

export default withRouter(Menus);

I have tried passing the value like so:

  gotoCoffee = (e) => {
    this.setState({isLoading:true,select:e})
    setTimeout(()=>{
      this.setState({isLoading:false,redirect:true})
    },5000) 
    console.log(this.state.select)
  }

an like so:

<div onClick={(c) => this.gotoCoffee(c)} 

or so:

<div onClick={(event => this.gotoCoffee(event.target.value} 

but console.log(this.state.select) shows me 'undefined' for both tries.

It appears that I'm passing the Class with 'c'.

browser shows me precisely that on the uri at redirect:

http://localhost/coffee/[object%20Object]


Now if I pass mapped 'c' to {this.renderCoffee(c)}, which not an onClick event, I manage to pass the array items.

But I need to pass not the object, but the clicked value 'c' to this.gotoCoffee(c), and THEN update this.state.select.

How do I fix this?

1
  • It looks like you can just pass c as an argument to this.gotoCoffee(), unless I'm missing something? Commented Aug 20, 2019 at 21:31

4 Answers 4

11
+50

You can pass index of element to gotoCoffee with closure in render. Then in gotoCoffee, just access that element as this.state.coffees[index].

gotoCoffee = (index) => {
    this.setState({isLoading:true, select: this.state.coffees[index]})
    setTimeout(()=>{
      this.setState({isLoading:false,redirect:true})
    },5000)
  }

  render(){
    const data = this.state.coffees;

    return (
      <div>
        <h1 className="title is-1"><font color="#C86428">Menu</font></h1>
        <hr/><br/>
        {data.map((c, index) => 
          <span key={c}>
            <div>
               {this.state.isLoading && <Brewing />}
               {this.renderCoffee()}
              <div onClick={() => this.gotoCoffee(index)} 
                  <strong><font color="#C86428">{c}</font></strong></div>
            </div>
          </span>)
        }
      </div>
    );
  }
}
Sign up to request clarification or add additional context in comments.

Comments

4

so based off your code you could do it a couple of ways.

onClick=(event) => this.gotoCoffee(event.target.value)

This looks like the approach you want.

onClick=() => this.gotoCoffee(c)

c would be related to your item in the array.

1 Comment

thank you, but could you please add this.setState({select:c}), or how it would be done, to your answer?
4

All the answers look alright and working for you and it's obvious you made a mistake by not passing the correct value in click handler. But since you're new in this era I thought it's better to change your implementation this way:

  • It's not necessary use constructor at all and you can declare a state property with initial values:

    class Menus extends Component{
        state= {
            /* state properties */
        };
    }
    
  • When you declare functions in render method it always creates a new one each rendering which has some cost and is not optimized. It's better if you use currying:

    handleClick = selected => () => { /* handle click */ }
    render () {
        // ...
        coffees.map( coffee =>
            // ...
             <div onClick={ this.handleClick(coffee) }>
            // ...
    }
    
  • You can redirect with history.replace since you wrapped your component with withRouterand that's helpful here cause you redirecting on click and get rid of renderCoffee method:

    handleClick = selected => () => 
        this.setState(
            { isLoading: true},
            () => setTimeout(
                () => {
                    const { history } = this.props;
                    this.setState({ isLoading: false });
                    history.replace(`/${coffee}`);
                }
                , 5000)
        );
    

Since Redirect replaces route and I think you want normal page change not replacing I suggest using history.push instead.

4 Comments

thank you for your answer. actually I need the constructor. This is not the full component code. I need to use this.props elsewhere. +1 for the tip on history.replace(), though. will think about using it instead of <Redirect>.
@data_garden glad to hear it's helpful. Try to use componentDidMount if possible
I guess your state declaration has a little issue state: {, as a class properties, it should be like state = {. thanks for your answer. I leave an upvote.
@AmerllicA You are right. such errors are not noticeable unless you have either error check on or a pair of hawk eyes ;)
0

You've actually almost got it in your question. I'm betting the reason your state is undefined is due to the short lived nature of event. setState is an asynchronous action and does not always occur immediately. By passing the event off directly and allowing the function to proceed as normal, the event is released before state can be set. My advice would be to update your gotoCoffee function to this:

 gotoCoffee = (e) => {
    const selectedCoffee = e.target.value
    this.setState({isLoading:true,select:selectedCoffee},() => 
     {console.log(this.state.select})
    setTimeout(()=>{
      this.setState({isLoading:false,redirect:true})
    },5000) 
  }

Note that I moved your console.log line to a callback function within setState so that it's not triggered until AFTER state has updated. Any time you are using a class component and need to do something immediately after updating state, use the callback function.

Comments

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.