React子组件更新状态时父组件出现状态未定义问题
.map is not a function报错 尝试根据按钮是否为“current player”设置点击后禁用按钮。PlayerButton组件的控制台日志能输出正确内容,但App.js中出现.map is not a function错误,说明初始渲染正常的playerData在点击后变为undefined(点击后在App.js中打印playerData也显示undefined)。
疑问:为何setPlayerData函数无法将数据传回父组件?考虑过使用async await或useEffect,但不确定如何在此场景下实现。曾尝试使用useEffect和async await,假设是父组件在子组件数据传递完成前就重新渲染,但如果是这样,不应该渲染旧数据吗?无法成功更新子组件中的状态以让父组件使用。
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;
问题原因
核心问题出在PlayerButton的handleButtonClick函数里:调用setPlayerData的回调时,你执行了playerData.map生成新数组,但没有把这个新数组返回给setPlayerData。map方法本身会返回新数组,但你的回调函数没有return这个结果,导致setPlayerData接收到的是undefined,直接覆盖了原来的playerData数组。后续渲染时playerData变成undefined,调用.map自然会抛出错误。
解决方案
修改PlayerButton中的handleButtonClick函数,确保回调返回map生成的新数组:
写法1:显式return
const handleButtonClick = () => { setPlayerData(playerData => { // 显式返回map生成的新数组 return playerData.map(player => { if (player.name === name) { console.log({ ...player, currentPlayer: false }) return { ...player, currentPlayer: false } } return player }) }) }
写法2:隐式返回(更简洁)
const handleButtonClick = () => { setPlayerData(playerData => playerData.map(player => player.name === name ? { ...player, currentPlayer: false } : player ) ) }
额外说明
不需要使用async await或者useEffect,这个问题和异步渲染无关,纯粹是状态更新时没有正确返回新的状态值。React的setState回调函数必须返回新的状态数据,才会用这个值去更新对应的state。
内容的提问来源于stack exchange,提问作者mbronstein1

