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
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:
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:
the first time
child
render, the prop passes on it equal to the init value you set (this is why you got undefined) until thefoo
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.my suggestion is to use the prop you passed into
child
because it’s already a state inparent
and any change in make rerender to youchild
if you need to update the prop from the
child
pass the setter function as propsetMyObject