Search code examples
use-statereact-functional-componentpreact

passing asyc props/state to child component


NOTE: ignore the first part go to the additional edit

I have a piece of state that is async generated and is changed based on another piece of async state. I can get the updated prop of the state that i set in the child but not the other that is async generated in the parent. Hard to explain, but here is an example i made.

app.tsx

import { useEffect, useState } from 'preact/hooks';
import { Child } from './Child';
import "./app.css"

export function App() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(0);

  useEffect(() => {
    const asycSetState = async () => {
      setTimeout(() => {
        setOtherState(count + 100);
      }, 1000);
    };
  }, [count, otherState]);

  return (
    <>
      <Child count={count} setCount={setCount} otherState={otherState} />
    </>
  );
}

Child.tsx

export const Child = ({ count, setCount, otherState }) => {
  return (
    <>
      <div>count: {count}</div>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        add
      </button>

      <h3>otherstate: {otherState}</h3>
    </>
  );
};

So in this example the Child will properly render the count but otherstate is not.

I have made a Stackblitz Here

Edit

Ok. That was my mistake. This example above works when you invoke asyncSetState(). I cannot replicate the problem i am having and i can't really post my actual project. But here is a small snip.

selectedRace is the state being changed in the child an tableData and raceName are updated in the parent

...
  useEffect(() => {
    (async () => {
      const result = await makeTableData();
      setTableData(result);
    })();
  }, [compsCol, selectedRace]);

  useEffect(() => {
    if (!raceLoading) {
      (async () => {
        const name = await raceDoc?.name;
        console.log("name: ", name);
        setRaceName(name);
      })();
    }
  }, [raceDoc]);

  const data = useMemo(() => {
    return tableData;
  }, [tableData]);

...

<Fragment>
  {console.log("index: ", raceName)}
  <FleetsTables
    raceName={raceName}
    setRaceName={setRaceName}
    tableData={tableData}
    serInfo={serInfo}
    setSelectedRace={setSelectedRace}
    selectedRace={selectedRace}
    {...rest}
  />
</Fragment>

So in this snippet the console will log out the new raceName (state) i am looking for, but in the FleetsTables component the state of raceName never changes.

I would add that the updating tableData state should take longer than raceName and it does correctly update in the children.

SO obviously there is not enough context to fix my code, but WHAT can be possible reasons for something like this to happen. I have tried everything i can think of with no luck.


Solution

  • Really simple issue: you never actually call asycSetState.

      useEffect(() => {
        const asycSetState = async () => {
          setTimeout(() => {
            setOtherState(count + 100);
          }, 1000);
        };
    +   asycSetState();
      }, [count, otherState]);
    

    Could also make that into an IIFE:

      useEffect(() => {
        (async () => {
          setTimeout(() => {
            setOtherState(count + 100);
          }, 1000);
        })();
      }, [count, otherState]);
    

    You should also remove otherState from the deps array. Your code will create an infinite loop, as you're setting otherState then the effect re-fires as a result.