Home > Software design >  Undefined state in parent when setting state in child - React
Undefined state in parent when setting state in child - React

Time:12-07

I am trying to disable a button onClick based on if the button is the "current player". I am able to successfully get the correct code in the console log from my PlayerButton component, but I also get errors from my App.js that .map is not a function, meaning that my playerData (which works on initial mount) becomes undefined somehow after the onClick (i also receive undefined when trying to log playerData in App.js after the onClick).

Any ideas as to why my setPlayerData function isn't passing the data back to the parent component? I am thinking I may need to either use async await or useEffect somewhere, but I'm not sure how to implement it in this case.

App.js

import React, { useState } from 'react';
import './app.css'
import PlayerButton from './components/PlayerButton';

function App() {
  const [playerData, setPlayerData] = useState([
    {
      name: "Player 1",
      currentPlayer: true,
      position: 0
    },
    {
      name: "Player 2",
      currentPlayer: false,
      position: 0
    },
    {
      name: "Player 3",
      currentPlayer: false,
      position: 0
    }
  ]);

  return (
    <div className="container">
      {playerData.map((player, index) => (
        <div key={player.name}>
          <PlayerButton player={player} index={index} setPlayerData={setPlayerData} />
        </div>
      ))}
    </div>
  );
}

export default App;

PlayerButton.js

import React from 'react';

const PlayerButton = ({ player: { name, currentPlayer }, index, setPlayerData }) => {

  const handleButtonClick = () => {
    setPlayerData(playerData => {
      playerData.map(player => {
        if (player.name === name) {
          console.log({ ...player, currentPlayer: false })
          return { ...player, currentPlayer: false }
        }
        return player
      })
    })
  }

  return (
    <button disabled={!currentPlayer} type="button" id={index   1} className={`player${index   1}-btn`} onClick={handleButtonClick}>{name}</button>
  )
}

export default PlayerButton;

I experimented with useEffect and async await because, I'm assuming, on update the parent component is trying to re-render before the data is successfully passed from the child. But if that were the case, wouldn't it just re-render the previous data?

Somehow, I'm not successfully updating the state in the child so the parent can use it.

CodePudding user response:

You've lost a return statement and returned undefined instead

Should be:

setPlayerData(playerData => {
  return playerData.map(player => {
    if (player.name === name) {
      console.log({ ...player, currentPlayer: false })
      return { ...player, currentPlayer: false }
    }
    return player
  })
})

CodePudding user response:

Ok I found a solution. I created a function in the parent component that uses the setPlayerData function inside a callback setCurrentPlayer, passed it to the child, and executed the callback inside the child.

App.js

  const setCurrentPlayer = () => {
    setPlayerData((playerData.map((player) => {
      if (player.currentPlayer) {
        return { ...player, currentPlayer: false}
      }
      return player
    })))
  }

PlayerButton.js

  const handleButtonClick = () => {
    setCurrentPlayer();
  }
  • Related