简体   繁体   English

React useState 没有正确更新我的子组件道具的 state

[英]React useState is not correctly updating the state of my child component props

I spent days trying to figure out what's wrong with my code and in doing so, tried multiple solutions/suggestions but no luck.我花了几天时间试图找出我的代码有什么问题,并在这样做时尝试了多种解决方案/建议,但没有运气。

UPDATE Reproduced the issue in the following codesandbox https://codesandbox.io/s/nostalgic-pond-u130qs?file=/src/selectTracker.tsx更新在以下代码和框中重现了问题https://codesandbox.io/s/nostalgic-pond-u130qs?file=/src/selectTracker.tsx

Summary of the issue I have a parent component with 2 children.问题总结我有一个有 2 个孩子的父组件。 The first child is responsible for capturing user's selection from a Select and invoking a setState wrapper in the parent.第一个孩子负责从 Select 中捕获用户的选择,并在父级中调用 setState 包装器。 The parent will then pass the user's selection as props to the second child which will use it to fetch data from an API and then display it in a table.然后,父母将用户的选择作为道具传递给第二个孩子,第二个孩子将使用它从 API 获取数据,然后将其显示在表格中。 The issue is that, the table will not re-render when the state changes but only when there is another selection.问题是,当 state 更改时,表格不会重新渲染,但只有在有另一个选择时才会重新渲染。 Meaning, I select value A, nothing happens, then I select value B, then the table renders data based on value A. I know React states handling is asynchronous but that should not explain the bug I'm facing.意思是,我 select 值 A,没有任何反应,然后我 select 值 B,然后表格根据值 A 呈现数据。我知道 React 状态处理是异步的,但这不应该解释我面临的错误。 Happy to hear your suggestions/potential fixes.很高兴听到您的建议/可能的修复。

Parent Component父组件

 const MainComponent = () => { const [trackerSetup, setTrackerSetup] = useState<TrackerSetup>(); return ( <> <Container maxWidth="lg"> <Grid direction="row" container alignItems="center" justifyContent="center" spacing={2} marginTop={5} > <Grid item xs={6}> <AddTrackerSetupComponent /> </Grid> <Grid item xs={6}> <SelectTrackerSetupComponent setTrackerSetup={setTrackerSetup} /> </Grid> </Grid> {trackerSetup? ( <TrackerTableComponent trackerSetup={trackerSetup} /> ): ( <span>Select a tracker setup</span> )} </Container> </> ); }; export default MainComponent;

Child 1孩子 1

 interface SelectTrackerSetupComponentProps { setTrackerSetup: any; } const SelectTrackerSetupComponent = ( props: SelectTrackerSetupComponentProps ) => { const [existingTrackerSetups, setExistingTrackerSetups] = useState<TrackerSetup[]>(); const [ selectedExistingTrackerSetupIndex, setSelectedExistingTrackerSetupIndex, ] = useState(0); useMemo(() => { const existingSetups = JSON.parse( localStorage.getItem("trackerSetup"); ) as TrackerSetup[]; setExistingTrackerSetups(existingSetups), }; []). return ( <> {existingTrackerSetups && existingTrackerSetups?length > 0. ( <FormControl fullWidth> <InputLabel id="existingSetups">Select an Existing Setup</InputLabel> <Select labelId="existingSetups" id="existingSetups-select" label="Existing Setups" value={selectedExistingTrackerSetupIndex} onChange={(e) => { setSelectedExistingTrackerSetupIndex(Number(e.target;value)). props.setTrackerSetup( existingTrackerSetups[Number(e.target;value)] ). }} > {existingTrackerSetups:map((trackerSetup, TrackerSetup. index) => { return ( <MenuItem key={trackerSetup.name} value={index}> {trackerSetup;name} </MenuItem> ): })} </Select> </FormControl> ). ( <span>Loading.;</span> )} </> ); }; export default SelectTrackerSetupComponent;

Child 2孩子 2

 interface TrackerTableComponentProps { trackerSetup: TrackerSetup; } const TrackerTableComponent = (props: TrackerTableComponentProps) => { const [trackers, setTrackers] = useState<Tracker[]>(); const [loading, setLoading] = useState(false); useEffect(() => { const loadTrackerData = async () => { setLoading(true); const fetchedTrackers = await getTrackerObject( props.trackerSetup.trackers ); console.log("Displaying the following trackers ", fetchedTrackers); setTrackers(fetchedTrackers); }; loadTrackerData(); setLoading(false); }, []); return ( <> {?loading: ( <TableContainer component={Paper}> <Table sx={{ minWidth. 650 }} aria-label="simple table"> <TableHead> <TableRow> <TableCell>Tracker ID</TableCell> <TableCell>FW version</TableCell> <TableCell>battery level</TableCell> <TableCell>Actions</TableCell> </TableRow> </TableHead> <TableBody> {trackers && trackers:map((tracker. Tracker) => { console:log("Table, displaying tracker "; tracker). return ( <TableRow key={tracker:id} sx={{ "&,last-child td: &:last-child th": { border. 0 } }} > <TableCell component="th" scope="row"> {tracker.id} </TableCell> <TableCell>{tracker.fwVersion}</TableCell> <TableCell>{tracker.batteryLevel;toString()} %</TableCell> <TableCell> <Button variant="contained" color="success" size="small" > Update FW </Button> </TableCell> </TableRow> ): })} </TableBody> </Table> </TableContainer> ). ( <span>Loading..;</span> )} </> ); }; export default TrackerTableComponent;

I tried to reproduce your issue with codesandbox.我试图用codeandbox重现你的问题。 There was a small and simple warning inside TrackerTableComponent: React Hook useEffect has a missing dependency: 'props.trackerSetup.trackers'. Either include it or remove the dependency array. TrackerTableComponent 中有一个小而简单的警告TrackerTableComponent: React Hook useEffect has a missing dependency: 'props.trackerSetup.trackers'. Either include it or remove the dependency array. TrackerTableComponent: React Hook useEffect has a missing dependency: 'props.trackerSetup.trackers'. Either include it or remove the dependency array. within your useEffect.在你的 useEffect 中。

useEffect(() => {
  const loadTrackerData = async () => {
    setLoading(true);
    const fetchedTrackers = await getTrackerObject(
      props.trackerSetup.trackers
    );
    console.log("Displaying the following trackers ", fetchedTrackers);
    setTrackers(fetchedTrackers);
  };
  loadTrackerData();
  setLoading(false);
}, []); // <- Here

If you change it to [props.trackerSetup.trackers] table will reflect changes as expected.如果将其更改为[props.trackerSetup.trackers]表将按预期反映更改。 But.但。 In your SelectTrackerSetupComponentProps you are loading items from localstorage and setting them to setExistingTrackerSetups , but you also have a selectedIndex there and you component renders with selected 0 item loaded.在您的SelectTrackerSetupComponentProps中,您正在从 localstorage 加载项目并将它们设置为setExistingTrackerSetups ,但您也有一个 selectedIndex ,并且您的组件渲染时加载了选定的 0 项目。 But you didnt call the passed props.setTrackerSetup to make the table render the actual data when initially loaded.但是您没有调用传递的props.setTrackerSetup来使表格在最初加载时呈现实际数据。 So changes to SelectTrackerSetupComponent :所以更改SelectTrackerSetupComponent

Not important but still better than "any" type不重要但仍然比“任何”类型好

interface SelectTrackerSetupComponentProps {
  setTrackerSetup: React.Dispatch<SetStateAction<TrackerSetup>>;
}

Extracted method from "props" object, i just prefer to extract everything from passed props.从“道具”object 中提取方法,我只是更喜欢从传递的道具中提取所有内容。

const { setTrackerSetup } = props;

Changed your useMemo to useEffect due to logically it suits better将您的 useMemo 更改为 useEffect,因为从逻辑上讲它更适合

useEffect(() => {

  // change it back, i used dummy data from constant
  const existingSetups =
    /*JSON.parse(
    localStorage.getItem("trackerSetup")!
  ) as TrackerSetup[];*/
    TRACKER_SETUPS;

  setExistingTrackerSetups(existingSetups);
  // Added to prevent future possible bugs
  setSelectedExistingTrackerSetupIndex(0); 
  // Added to tell parent component that actual item is now selected
  setTrackerSetup(existingSetups[0]);
}, [setTrackerSetup]);

编辑 twilight-star-3w8vf2

Update: After codesandbox provided the root issue of errors was found:更新:在codesandbox提供后发现错误的根本问题:

useEffect(() => {
  const fetchData = async () => {
    setLoading(true);
    let list: Tracker[] = [];
    // Error is just below...
    props.trackerSetup.trackers.forEach(async (tracker) => {
      const fetchedTracker = await getMockData();
      console.log(fetchedTracker);
      list.push({
        id: "tracker ID",
        fwVersion: fetchedTracker.data.fw_version,
        batteryLevel: fetchedTracker.data.battery_level
      });
    });
    setTrackers(list);
    setLoading(false);
  };
  fetchData();
}, []);

props.trackerSetup.trackers.forEach(async (tracker) => { will not work in that way. props.trackerSetup.trackers.forEach(async (tracker) => {不会以这种方式工作。

Correct code:正确代码:

useEffect(() => {
  const fetchData = async () => {
    setLoading(true);
    // CHANGE: Foreach is not working nice with async.
    // Converting to promises instead.
    const promises = props.trackerSetup.trackers.map(async (tracker) => {
      const fetchedTracker = await getMockData();
      console.log(fetchedTracker);
      const result: Tracker = {
        // CHANGE: That will cause a duplicate key error. Changed to random
        id: (Math.random() * (10000 - 1) + 1).toString(),
        fwVersion: fetchedTracker.data.fw_version,
        batteryLevel: fetchedTracker.data.battery_level
      };
      return result;
    });
    const list = await Promise.all(promises);
    setTrackers(list);
    setLoading(false);
  };
  fetchData();
  // CHANGE: You didnt set needed item in depsArray as i described in answer
  // React will not re-execute this hook on trackers changed without it
}, [props.trackerSetup.trackers]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM