So, I was refactoring a React component after reading more about React principles of lifting state up and in particular after reading this about derived state.
I had a component like so (before the refactor) that was working but now I realize was duplicating state among multiple sources of truth (in the parent and the child):
const ChildComponent = ({
prevStep,
nextStep,
parentStateData,
updateParentStateData,
}) => {
const [childState, updateChildState] = useState(
parentStateData["thisChildData"]
);
const updateParentStateDataFromChild = () => {
return updateParentStateData(
Object.assign(parentStateData, { thisChildData: childState })
);
};
useEffect(() => updateParentStateDataFromChild(), [childState]);
return (
<div className="text-center">
<h1 className="step-title">Choose Child State</h1>
<div class="state-size-buttons">
<Button
onClick={() => updateChildState(1)}
active={childState === 1}
className="mt-3"
variant="light"
size="lg"
>
1
</Button>
<Button
onClick={() => updateChildState(2)}
active={childState === 2}
className="mt-3"
variant="light"
size="lg"
>
2
</Button>
<Button
onClick={() => updateChildState(3)}
active={childState === 3}
className="mt-3"
variant="light"
size="lg"
>
3
</Button>
</div>
<Button onClick={() => prevStep()} className="mr-2" variant="light">
Go Back
</Button>
<Button disabled={!childState} onClick={() => nextStep()} variant="light">
Next
</Button>
</div>
);
};
Again, this worked but it's not good (I think) because it duplicates state. The ChildComponent
receives the parent state (parentStateData
) and an update function for that state (updateParentStateData
) but duplicates that state in its own childState
and updateChildState
- which in turn updates the parent using a useEffect()
hook -> when child state updates, update the parent state.
So, I started my refactor, like this:
const ChildComponent = ({
prevStep,
nextStep,
parentStateData,
updateParentStateData,
}) => {
const updateState = (data) => {
return updateParentStateData(
Object.assign(parentStateData, { thisChildData: data })
);
};
const nextDisabled = !parentStateData["thisChildData"];
return (
<div className="text-center">
<h1 className="step-title">Choose A State</h1>
<div class="state-size-buttons">
<Button
onClick={() => updateState(1)}
active={parentStateData["thisChildData"] === 1}
className="mt-3"
variant="light"
size="lg"
>
1
</Button>
<Button
onClick={() => updateState(2)}
active={parentStateData["thisChildData"] === 2}
className="mt-3"
variant="light"
size="lg"
>
2
</Button>
<Button
onClick={() => updateState(3)}
active={parentStateData["thisChildData"] === 3}
className="mt-3"
variant="light"
size="lg"
>
3
</Button>
</div>
<Button onClick={() => prevStep()} className="mr-2" variant="light">
Go Back
</Button>
<Button
disabled={nextDisabled}
onClick={() => nextStep()}
variant="light"
>
Next
</Button>
</div>
);
};
This time as you can see, there is no internal state and the parent state object simply gets updated at the relevant key using Object.assign()
.
But my problem is these state changes never get reflected in my view. If I do a React component inspector I see the state is changing (although it seems to lag) but the view doesn't change. Most critically, the nextDisabled
constant and variants of that (I tried putting that logic directly into the Next button <Button disabled={nextDisabled} onClick={()=>nextStep()} variant="light">Next</Button>
) don't ever change. So the parent state gets updated, but !parentStateData['thisChildData']
never changes value or never runs again... so what gets rerendered when the props (via the change in parentStateData
) change? Why doesn't the simple validation on the next button work as I expect when I update the parent state? That is, once I select a number using the buttons (no longer 0
from the default) I would expect it to not be disabled (example: !1
is false - so not disabled as its for the disabled
prop).
But it never changes!
It stays evaluated to true
despite the state updating in the parent and therefore the props changing in the child. So somewhere I have a conceptual error. Any help here would be very much appreciated.