Search code examples
javascriptreactjsstatedom-eventssetstate

Change state from dynamically created child component - React


In this app, you can change the football matches score by clicking the GOAL button of a particular game. The problem is that my matches are saved in the state and I render them with the map method and I can't use setState dynamically for each match.

App.js

import React from 'react';

import Match from './components/Match';

import './App.css';

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      games: [
        {
          home: {
            team: "Bruges",
            score: 0
          },
          guests: {
            team: "Real Madrid",
            score: 0
          }
        },
        {
          home: {
            team: "Atletico Madrid",
            score: 0
          },
          guests: {
            team: "Lokomotiv",
            score: 0
          }
        }
      ]
    };

    this.goal = this.goal.bind(this);
  };

  goal(parent) {
    let rand = Math.round(Math.random());

    if(rand) {
      this.setState((prevstate) => ({parent:  prevstate.parent.home.score + 1}))
    } else {
      this.setState((prevstate) => ({parent: prevstate.parent.guests.score + 1}))
    }
  }

  render() {
    return (
      <div className="App">
        {this.state.games.map((item, index) => {
          return (<Match 
            key={index}

            home={item.home.team}
            homeScore={item.home.score}

            guests={item.guests.team}       
            guestsScore={item.guests.score}

            goal={() => this.goal(item)}
          />);
        })}
      </div>
    );
  }

}

Match.js

import React from 'react';

import '../App.css'

const Match = props => {
    return(
    <div className="match-container">
        <span className="name-container">{props.home} </span>
        <span className="score-container">{props.homeScore}</span>

        <button onClick={props.goal}>GOAL</button>

        <span className="score-container">{props.guestsScore} </span>
        <span className="name-container">{props.guests} </span>
    </div>
    );
}

export default Match;

CodeSandBox

I think my problem is in the goal method because I don't think that I can pass an argument and use it in setState.

I didn't find an answer related to this particular problem.


Solution

  • The problem is that you are trying to update an array as if it were an object. The fact is that you have set games as an array in state. Here is how you update it:

    goal(parent) {
        let rand = Math.round(Math.random());
        const { games } = this.state;
        const index = games.findIndex(element => element === parent);
        const newGames = [...games];
        if (rand) {
          newGames[index].home.score = newGames[index].home.score + 1;
        } else {
          newGames[index].guests.score = newGames[index].guests.score + 1;
        }
        this.setState({ games: newGames });
      }
    

    Here is a working demo https://codesandbox.io/s/practical-margulis-n0ch1