How would one update the value of variable simulationOn
inside of function executeSimulation
in the following context:
App this.state.simulationOn
changes via external code --> ... --> React stateless component ( Robot
) rerendered --> useEffect
hook called with new values --> executeSimulation
IS NOT UPDATED with new value of simulationOn
.
function Robot({ simulationOn, alreadyActivated, robotCommands }) {
useEffect(() => {
function executeSimulation(index, givenCommmands) {
index += 1;
if (index > givenCommmands.length || !simulationOn) {
return;
}
setTimeout(executeSimulation.bind({}, index, givenCommmands), 1050);
}
if (simulationOn && !alreadyActivated) {
executeSimulation(1, robotCommands);
}
}, [simulationOn, alreadyActivated, robotCommands]);
}
In the example above, simulationOn
never changes to false
, even though useEffect is called with the updated value (I check with console.log). I suspect this is because the new value of simulationOn
is never passed to the scope of function executeSimulation
, but I don't know how to pass new hook values inside of function executeSimulation
.
The executeSimulation function has a stale closure simulationOn will never be true, here is code demonstrating stale closure:
var component = test => { console.log('called Component with',test); setTimeout( () => console.log('test in callback:', test), 20 ); } component(true); coponent(false)
Note that Robot
is called every time it renders but executeSimulation
runs from a previous render having it's previous simulationOn
value in it's closure (see stale closure example above)
Instead of checking simulationOn
in executeSimulation
you should just start executeSimulation
when simulationOn
is true and clearTimeout in the cleanup function of the useEffect:
const Component = ({ simulation, steps, reset }) => { const [current, setCurrent] = React.useState(0); const continueRunning = current < steps.length - 1 && simulation; //if reset or steps changes then set current index to 0 React.useEffect(() => setCurrent(0), [reset, steps]); React.useEffect(() => { let timer; function executeSimulation() { setCurrent(current => current + 1); //set timer for the cleanup to cancel it when simulation changes timer = setTimeout(executeSimulation, 1200); } if (continueRunning) { timer = setTimeout(executeSimulation, 1200); } return () => { clearTimeout(timer); }; }, [continueRunning]); return ( <React.Fragment> <h1>Step: {steps[current]}</h1> <h1>Simulation: {simulation ? 'on' : 'off'}</h1> <h1>Current index: {current}</h1> </React.Fragment> ); }; const App = () => { const randomArray = (length = 3, min = 1, max = 100) => [...new Array(length)].map( () => Math.floor(Math.random() * (max - min)) + min ); const [simulation, setSimulation] = React.useState(false); const [reset, setReset] = React.useState({}); const [steps, setSteps] = React.useState(randomArray()); return ( <div> <button onClick={() => setSimulation(s => !s)}> {simulation ? 'Pause' : 'Start'} simulation </button> <button onClick={() => setReset({})}>reset</button> <button onClick={() => setSteps(randomArray())}> new steps </button> <Component simulation={simulation} reset={reset} steps={steps} /> <div>Steps: {JSON.stringify(steps)}</div> </div> ); }; ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>
simulationOn is never changed, because the parent component has to change it. It is passed in a property to this Robot component. I created a sandbox example to show, if you change it properly in the parent, it will propagate down. https://codesandbox.io/s/robot-i85lf .
There a few design issues in this robot. It seems assume a Robot can "remember" the index value by holding it as a instance variable. React doesn't work that way. Also, it assume that useEffect will be called exactly once when one parameter is changed, which is not true. We don't know how many times useEffect will be invoked. React only guarantee that it will be called if one of the dependencies are changed. But it could be invoked more often.
My example shows that a command list has to be kept by the parent, and the full list needs to be sent, so the dumb Robot can show all the commands it executed.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.