Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - change this.state onClick rendered with array.map()

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?

like image 872
8-Bit Borges Avatar asked Aug 20 '19 21:08

8-Bit Borges


Video Answer


3 Answers

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>
    );
  }
}
like image 90
mukesh210 Avatar answered Oct 21 '22 20:10

mukesh210


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.

like image 25
Thomas Valadez Avatar answered Oct 21 '22 19:10

Thomas Valadez


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.

like image 4
Morteza Tourani Avatar answered Oct 21 '22 20:10

Morteza Tourani