skip to Main Content

I’m working with React and TypeScript. I’m new to React and I’m not sure how to ensure that a child component sets state properly based on a prop passed to it.

I am passing an object from a parent component into a child component as a prop. The parent component has an Object with keys and empty values. An example of this is below –

emptyObject = {
    id: '',
    name: '',
    timestamp: ''
}

An async function foo queries a database and populates the values based on the component’s prop, then returns a Promise of the updated Object. The updated Object should then be passed to the child component, where values are stored as State.

function ParentComponent({propName}) {
    const [myObject, setMyObject] = useState(emptyObject);

    useEffect( () => {
        foo(emptyObject).then((x) => {
            setMyObject(x);
        });
    }, [propName]);

    return (
        <>
            <ChildComponent obj = {myObject} />
        </>
    );
}


function ChildComponent({obj}) {
    const {id, name, timestamp} = obj;
    console.log("id: " + id + ". name: " + name) // this gives the correct values
    
    const [myId, setMyId] = useState(id)
    const [myName, setMyName] = useState(name);
    console.log("id: " + myId + ". name: " + myName) // this gives "id: undefined. name: undefined"


    return (
        <displays a form that is pre-populated with the id and name values>
    )
}

The issue I face is that destructuring the Object gives me the correct values, but the values in State are undefined. I suspect this is because setting State in React is asynchronous, but I’m not sure how to address this.

The first thing I tried was adding a hook in the child Component, but the same issue occurred.

function ChildComponent({obj}) {
    const {id, name, timestamp} = obj;
    const [myId, setMyId] = useState(id)
    const [myName, setMyName] = useState(name);
    
    useEffect( () => {
        setMyId(id);
        setMyName(name);
    }, [obj]);

I also tried implementing two useEffects in the parent and modifying the logic in the original useEffect to ensure that values updated but the same result still happens.

const [myObject, setMyObject] = useState(emptyObject);
const [updatedObject, setUpdatedObject] = useState(emptyObject);

    useEffect( () => {
       foo(emptyObject).then((x) => {
           setUpdatedObject(x);
       });
    }, [propName]);

    useEffect( () => {
        setMyObject(updatedObject);
    }, [updatedObject]);

    return (
        <>
            <ChildComponent obj = {myObject} />
        </>
    );

2

Answers


  1. You should not render the ChildComponent before the object is properly loaded. Therefor you can simply check, if the object is still empty or has been filled and make your ChildComponent dependent on that information, for example like this:

    return (
        <>
            {myObject.id !== '' && <ChildComponent obj={myObject} setObj={setMyObject} />}
        </>
    );
    

    You can also technically get rid of the additional states in the ChildComponent and just pass down your Set-Function for the object as well (see above). However the ParentComponent will be rerendered if anything changes. If you have a very performance-dependent application, this might be not the best way to do it.

    Besides that, if you update a state with an object, you should always create a new Object, to avoid side effects. Set it like this:

    setMyObject({...x});
    
    Login or Signup to reply.
  2. the first time child render, the prop passes on it equal to the init value you set (this is why you got undefined) until the foo function set the prop. useState hook initialize only first and when the init value is updated the state isn’t updated.

    you can resolve this by listening for prop updates in child like this.

    function ChildComponent({obj}) {
        const [myId, setMyId] = useState('')
        const [myName, setMyName] = useState('');
        
        useEffect(()=>{
          setMyName(obj.name) 
          setMyId(obj.id) 
        },[obj]);    
    
        return (
            <displays a form that is pre-populated with the id and name values>
        )
    }
    

    my suggestion is to use the prop you passed into child because it’s already a state in parent and any change in make rerender to you child

    function ChildComponent({ obj,  }) {
      const myId = obj.id; // or use it directly `obj.id`
      const myName = obj.name; // or use it directly `obj.name`
        
         .....
    }
    

    if you need to update the prop from the child pass the setter function as prop setMyObject

    function ChildComponent({obj, setMyObject}) {....}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search