Example is simple functional component that use useState to keep click counter.
Stepping through Stepper MUI component, I want to create instance of Example component with different init value e.g. at step 0, init value 100, at step 1, init value 111, at step 2, init value 112.
While stepping through each case of step, despite passing different initial value, Example functional component keep state only to first init value i.e. 100.
File is /Components/Navigation/Stepper01a.js, Example component being referenced in StepContent, which is being referenced in HorizontalLinearStepper component. Overall code is example from Material UI Stepper component. Am just trying to test it to create different instance of other functional component (Example in this case) with different initial values at each step.
Example component:
function Example({ init }) {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = React.useState(init)
return (
<div>
<p>init {init} </p>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
StepContent Component:
function StepContent({ step }) {
console.log("step", step)
switch (step) {
case 0:
return <Example init={100} />
case 1:
return <Example init={111} />
case 2:
return <Example init={112} />
default:
return "Unknown step"
}
}
HorizontalLinearStepper Component:
export default function HorizontalLinearStepper() {
...
<div>
<StepContent step={activeStep} />
</div>
...
}
See running example https://mj3x4pj49j.codesandbox.io/ code https://codesandbox.io/s/mj3x4pj49j
Upon clicking Next at step 0, count is expected to set with init value as 111 but it remains 100 and upon clicking Next at step 1, count is expected to set with init value 112 but again it remains 100. It seems that once state count is initialized to 100 at step 0, same state is being used in step 1 and 2 i.e. state is not isolated.
Does this problem occur due to violation of Rules of Hooks somehow?
Inside the component you are doing correct. Init value taken from props
- it's common practice.
But see, useState()
applies init value only on initial render(componentDidMount
or constructor
for class-based components). The way you use that component you did not re-create <Example
but update that.
So in other words the same case you'd get having class-based component without componentDidUpdate
: React updates existing <Example>
instead of re-creating that and does not apply componentDidMount
anymore(for your case it does not init useState
with initial value).
I see different way to handle that.
key
. Hacky but working way:switch (step) {
case 0:
return <Example key="step-1" init={100} />
case 1:
return <Example key="step-2" init={111} />
case 2:
return <Example key="step-3" init={112} />
default:
return "Unknown step"
}
useEffect
as hook-version of componentDidUpdate
:useEffect(() => {
setCount(init);
}, [init]);
Here is a tricky moment since you are counting clicks. So making just setCount(init)
might be not good move and will break your calculation. So actual code may be much more complicated. I'm not sure here since don't understand the logic behind counting.
count
to parent component) you will not need to update anything after init
is updated.You need to give a hint to React that you need different instances of Example component during different steps. This hint can be done like so:
function StepContent({ step }) {
console.log("step", step)
switch (step) {
case 0:
return <Example init={100} key={0} />
case 1:
return <Example init={111} key={1} />
case 2:
return <Example init={112} key={2}/>
default:
return "Unknown step"
}
}
The reason is that React is seeing an Example in virtual dom in all cases and according to its algorithm, it thinks it is the same component but with different prop so he just changes its state and does not re-init it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With