React: Function not reading updated global variable - javascript

Run button has already been clicked.
When I click on the skip button I am unable to reach Switch Case 2. The component state skip gets updated but the function still prints the old value of skip.
const Component = () => {
const [skip, setskip] = useState(false);
const [runstate, setrunstate] = useState(1);
const run = async () => {
switch(runstate) {
case 1: {
if(skip) {
setrunstate(2);
}
else {
console.log(skip , "Stuck in Step 1") // false, Stuck in Step 1 even after clicking skip
setTimeout(run, 250)
}
break;
}
case 2: {
console.log("Reached Step 2")
}
}
}
return (
<>
<button onClick={run}> Run </button>
<button onClick={() => setskip(true)}> Skip </button>
</>
)
}
Can anybody tell what might be causing this or a correct way to achieve this?

Why recursion is not working?
There are a lot of factors behind this. So, let's understand with a minimal reproducible example of your use case and breaking the code step-by-step.
const [foo, setFoo] = useState(0);
const recursiveCallback = useCallback(() => {
setFoo(foo + 1);
// an infinite recursion occurs as
// termination condition never satisfies
if (foo <= 2) {
// current reference is being called
// with the current value of foo in
// the scope, i.e. lexical environment
recursiveCallback();
}
}, [foo]);
return (
<>
<p>{foo}</p>
<button onClick={recursiveCallback}>Recursive Callback</button>
</>
);
When recursiveCallback() is called for the very first time, a closure is created with the foo's value being 0.
When foo is incremented with setFoo(foo + 1), a new reference of recursiveCallback is created in the memory (let's say x) which would have the updated value of foo in it.
Now, when recursiveCallback() is called again, it does not call the x reference of itself but the current reference from where it's being called from, which would have the value of foo present in the lexical environment, i.e. 0. So, it appears that foo is not incrementing but in actual it is, as you can see in the <p>{foo}</p>.
So, the solution to fix the above snippet would be to call the function in a useEffect which would always call a new reference of nonRecursiveCallback every time the value of foo is updated!
// note that the function is no more recursive
const nonRecursiveCallback = useCallback(() => {
// updated foo is logged
console.log(foo);
// incrementing foo
setFoo(foo + 1);
}, [foo]);
useEffect(() => {
if (foo && foo <= 2) {
// new reference is being called
// with an updated value of foo
nonRecursiveCallback();
}
}, [foo, nonRecursiveCallback]);
Solution
Instead of calling run() recursively, you can have it called repetitively with the help of useEffect hook unless the termination condition is not satisfied.
const { useState, useEffect, useCallback } = React;
const Component = () => {
const [skip, setSkip] = useState(false);
const [runState, setRunState] = useState(1);
// two additional local states have been introduced
// which would help trigger useEffect repetitively
// after user has clicked on run button
const [runEffect, setRunEffect] = useState(false);
const [toggleEffect, setToggleEffect] = useState(false);
useEffect(() => {
if (runEffect) {
switch (runState) {
case 1: {
if (skip) {
setRunState(2);
} else {
console.log(skip, 'Stuck in Step 1');
// toggle a boolean value which
// would trigger this hook again
setTimeout(() => setToggleEffect(!toggleEffect), 250);
}
break;
}
case 2: {
console.log('Reached Step 2');
setRunEffect(false);
break;
}
default:
break;
}
}
}, [runEffect, toggleEffect, runState, skip]);
const startRecursion = useCallback(() => {
setRunEffect(true);
setToggleEffect(true);
}, []);
const handleSkip = useCallback(() => {
setSkip(true);
}, []);
return (
<React.Fragment>
<button onClick={startRecursion}>Run</button>
<button onClick={handleSkip}>Skip</button>
</ React.Fragment>
);
};
// Render it
ReactDOM.render(
<Component />,
document.getElementById("react")
);
<div id="react"></div>
<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>

Related

can't get updated state value inside method

I wanna stop the looping when stopState is true.
The stopState is updated when I click the stop button. but inside the startIncrement method, the stopState is always false.
This is my code:
function App() {
const [num, setNum] = useState(0)
const [stop, setStop] = useState(false)
function Delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function startIncrement(){
for(let i=0; i<100; i++){
console.log(stop) // always false even when i click the stop button
if(stop) i = 2000;
setNum(i)
await Delay(1000)
}
}
return (
<main>
<p>stop: {stop ? "true" : "false"}</p>
<p onClick={startIncrement}>{num}</p>
<button onClick={() => setStop(true)}>stop</button>
</main>
);
}
Short answer:
In order to stop the previously excuted function, you will have to invoke a `clean up` function.
Detail on how it works:
In react, each render has it's own props and state, and we can explain this in action:
First, you excuted the function startIncrement(), at the time of excution, and at that perticular render phase, the value of stop is false;
Every time the num value changes, the page renders, and it just keep going... (excute below code, you can see console prints "renders!")
Then you click setStop(true) button, and at this particular render, stop === true.
However, these two steps involves two different renders, and each renders' state and everything else (props, effect...), does not affect each other, therefore, your stop value in the function never changes.
Here's an alternative to achieve the same:
export default function App() {
const [num, setNum] = useState(0)
const [stop, setStop] = useState(null)
console.log("renders!")
useEffect(() => {
const run = setInterval(() => {
if(stop === false && num < 100) setNum(num + 1);
}, 1000);
return () => clearInterval(run) // clean up function
}, [num, stop]);
return (
<main>
<p>stop: {stop ? "true" : "false"}</p>
<p>{num}</p>
<button onClick={() => setStop(false)}>start</button>
<button onClick={() => setStop(true)}>stop</button>
</main>
);
}
The clean up function in useEffect can be seen as a "undo" function.
Sandbox here, you can read more about side effect from this post in Dan Abramov's blog

Why react useEffect can memorize the state value?

Here's an example:
function Page() {
const [a, setA] = React.useState(0);
useEffect(() => {
const interval = setInterval(() => { console.log(a) }, 2000)
return () => clearInterval(interval)
}, []);
return (
<div>
<span>{a}</span>
<button onClick={() => setA(Math.random())}>button</button>
</div>
);
}
The invertal always log 0 despite the fact that count state variable has actually being increased by clicked the button a few times.
And many people say this is because the interval function capture a. But I really can not undersand it. When a variable can not be found in current environment, it will resolve to parent (or global).
And look at this example in contrast:
function Page() {
let a = 0;
useEffect(() => {
const interval = setInterval(() => { console.log(a) }, 2000)
return () => clearInterval(interval)
}, []);
return (
<div>
<button onClick={() => a = Math.random()}>button</button>
</div>
);
}
This is an obvious example for my expression. The interval closure capture the outer a variable. It will log the fresh value as it changes.
So how react useEffect implement this feature?
As my view (a bad pseudocode):
const hooks = [];
function useState(val) {
let state = hooks[0] || val;
hooks[0] = state;
function setVal(v) {
state = v;
hooks[0] = state;
}
return [state, setVal];
}
let cleanup = null;
function useEffect(callback) {
if (cleanup) cleanup()
cleanup = callback();
}
function Foo() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => { console.log(count)}, 1000);
})
return setCount;
}
var setCount = Foo(); // log 0
setCount(1);
Foo(); // log 1
But this is not way react was implement. So how react implement this?
Each time the component is rendered, the function is called.
First a value is assigned to a. This is either 0 (the first time) or the current value from the state (in subsequent times).
const [a, setA] = React.useState(0);
Then there is a useEffect hook. This takes two arguments. A function and a dependency array.
The function will be called every time the values in the dependency array change.
In your example, the dependency array is []. So the values never change. This means the function will only be called on the first render.
During the first render, the function it called. The value of a is 0.
The function passes a function to to setInterval. This function reads a. It has closed over the a variable from the first render.
In subsequent renders of the function, there will be a new a variable (belonging to this call to the function) which is assigned whatever value is in the state. The effect hook doesn't run again though and the existing interval is still looking at the original a variable.
In your second example, you are mutating the value of the original a variable (which is the variable the interval has closed over).

React useState does not update value

I am a bit confused as to why this component does not work as expected:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // This effect depends on the `count` state
}, 1000);
return () => clearInterval(id);
}, []); // 🔴 Bug: `count` is not specified as a dependency
return <h1>{count}</h1>;
}
but rewriting as below works:
function Counter() {
const [count, setCount] = useState(0);
let c = count;
useEffect(() => {
const id = setInterval(() => {
setCount(c++);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
React documentation says:
The problem is that inside the setInterval callback, the value of count does not change, because we’ve created a closure with the value of count set to 0 as it was when the effect callback ran. Every second, this callback then calls setCount(0 + 1), so the count never goes above 1.
But the explanation does not make sense. So why the first code does not update count correctly but the second does?
(Also declaring as let [count, setCount] = useState(0) then using setCount(count++) works fine too).
Why it looks like it doesn't work?
There are a couple hints that can help understand what's going on.
count is const, so it'll never change in its scope. It's confusing because it looks like it's changing when calling setCount, but it never changes, the component is just called again and a new count variable is created.
When count is used in a callback, the closure captures the variable and count stays available even though the component function is finished executing. Again, it's confusing with useEffect because it looks like the callbacks are created each render cycle, capturing the latest count value, but that's not what's happening.
For clarity, let's add a suffix to variables each time they're created and see what's happening.
At mount time
function Counter() {
const [count_0, setCount_0] = useState(0);
useEffect(
// This is defined and will be called after the component is mounted.
() => {
const id_0 = setInterval(() => {
setCount_0(count_0 + 1);
}, 1000);
return () => clearInterval(id_0);
},
[]);
return <h1>{count_0}</h1>;
}
After one second
function Counter() {
const [count_1, setCount_1] = useState(0);
useEffect(
// completely ignored by useEffect since it's a mount
// effect, not an update.
() => {
const id_0 = setInterval(() => {
// setInterval still has the old callback in
// memory, so it's like it was still using
// count_0 even though we've created new variables and callbacks.
setCount_0(count_0 + 1);
}, 1000);
return () => clearInterval(id_0);
},
[]);
return <h1>{count_1}</h1>;
}
Why does it work with let c?
let makes it possible to reassign to c, which means that when it is captured by our useEffect and setInterval closures, it can still be used as if it existed, but it is still the first one defined.
At mount time
function Counter() {
const [count_0, setCount_0] = useState(0);
let c_0 = count_0;
// c_0 is captured once here
useEffect(
// Defined each render, only the first callback
// defined is kept and called once.
() => {
const id_0 = setInterval(
// Defined once, called each second.
() => setCount_0(c_0++),
1000
);
return () => clearInterval(id_0);
},
[]
);
return <h1>{count_0}</h1>;
}
After one second
function Counter() {
const [count_1, setCount_1] = useState(0);
let c_1 = count_1;
// even if c_1 was used in the new callback passed
// to useEffect, the whole callback is ignored.
useEffect(
// Defined again, but ignored completely by useEffect.
// In memory, this is the callback that useEffect has:
() => {
const id_0 = setInterval(
// In memory, c_0 is still used and reassign a new value.
() => setCount_0(c_0++),
1000
);
return () => clearInterval(id_0);
},
[]
);
return <h1>{count_1}</h1>;
}
Best practice with hooks
Since it's easy to get confused with all the callbacks and timing, and to avoid any unexpected side-effects, it's best to use the functional updater state setter argument.
// ❌ Avoid using the captured count.
setCount(count + 1)
// ✅ Use the latest state with the updater function.
setCount(currCount => currCount + 1)
In the code:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// I chose a different name to make it clear that we're
// not using the `count` variable.
const id = setInterval(() => setCount(currCount => currCount + 1), 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
There's a lot more going on, and a lot more explanation of the language needed to best explain exactly how it works and why it works like this, though I kept it focused on your examples to keep it simple.
More on closures.
useRef makes it easy
function Counter() {
const countRef = useRef(0);
useEffect(() => {
const id = setInterval(() => {
countRef.current++;
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{countRef.current}</h1>;
}

How to call a function every x seconds with updated state (React)

Im having a lot of trouble with this and i have tried various things. I want to call a function every second after i have clicked a start button and then have it paused after i click a stop button. I keep getting weird behaviour that i cant explain.
How can i do this in react without classes?
somethings i have treid:
const simulation = () => {
if (!running) {
console.log('hit');
return
} else {
// console.log(grid);
console.log('hey');
setTimeout(simulation, 1000)
}
}
and
enter setInterval(() => {
let newGrid = [...grid]
for (let i = 0; i < numRow; i++) {
for (let k = 0; k < numCol; k++) {
let n = 0;
}
}
console.log(grid);
}, 5000)
I have tried a lot more, In some cases it would update the state should i have added to it but not updated it after i reset the state.
How can i call a function to run every one second with updated values of state * Note the function that i want to run will update the state
You may do the following:
keep track of the current counter value along with the counter on/off state in your component state;
employ useEffect() hook to be called upon turning counter on/off or incrementing that;
within useEffect() body you may call the function, incrementing count by one (if ticking is truthy, hence timer is on) with delayed execution (using setTimeout());
once count variable is changed in the state, useEffect() gets called once again in a loop;
in order to clean up the timer upon component dismantle, you should return a callback, clearing the timer from useEffect()
const { useState, useEffect } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const App = () => {
const [ticking, setTicking] = useState(true),
[count, setCount] = useState(0)
useEffect(() => {
const timer = setTimeout(() => ticking && setCount(count+1), 1e3)
return () => clearTimeout(timer)
}, [count, ticking])
return (
<div>
<div>{count}</div>
<button onClick={() => setTicking(false)}>pause</button>
<button onClick={() => setTicking(true)}>resume</button>
</div>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Late reply but maybe interesting for some people.
Look at npm package cron. In this case every 15min As easy as:
const [job] = useState(new cron.CronJob("0 */15 * * * *",async ()=>{
await updateRiderCoords();
}));
useEffect(() => {
job.start();
}, []);

Functional Component: Write functions inside or outside the component?

I often wrote functional components following a 'Class architecture' where all my function that concern the component are written inside of it like a method in a class.
For example, I have here a function counterAsFloat that is related to the Counter component. As you see I just wrote it inside of the component:
export default function Counter() {
const [counter, setCounter] = React.useState(0);
const counterAsFloat = () => {
return counter.toFixed(2);
};
return (
<div className="counter">
<h1>{counterAsFloat()}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
But actually I could also just declare the function outside the component and use it with a parameter:
const counterAsFloat = (counter) => {
return counter.toFixed(2);
};
export default function Counter() {
const [counter, setCounter] = React.useState(0);
return (
<div className="counter">
<h1>{counterAsFloat(counter)}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
So are there any pros or cons to write the functions outside the functional component?
This question is pretty opinion-based but there are few notes that you need to think about.
Declaring the function outside of scope is foremost for readability and reusability.
// Reuse logic in other components
const counterAsFloat = (counter) => {
return counter.toFixed(2);
};
// If Counter has complex logic, you sometimes want to compose it
// from functions to make it more readable.
export default function Counter() {
...
return (...);
}
One can argue that the first option is less performant because you declare the function on every render:
export default function Counter() {
...
// declare the function on every render
const counterAsFloat = () => {
return counter.toFixed(2);
};
return (...);
}
Such case is premature optimization. Check out JavaScript closures performance which relates to this.
Note that in this specific case, inlining the function is much better approach.
export default function Counter() {
...
return (
<div>
<h1>{counter.toFixed(2)}</h1>
...
</div>
);
}
While you could want to use outside functions for organization or reusability, it stills seems to go against the structure of functional components, at least for one reason: in functional components, states are immutable. So they normally are constants. And while your 2 functions seem to be somewhat similar, they differ greatly, precisely regarding this specific feature. Take for example this code:
const a = 2;
function increment(){
return ++a;
}
increment();
This is obviously forbidden, you cannot change a constant.
Write it differently:
const a = 2;
function increment(a){
return ++a;
}
increment(a);
The last one is allowed. It won't give the result you expect, at least looking at it rapidly, but it'll compile and won't have any runtime error.
Transpose this to your example. Let's say that you begin by wanting to simply output yourt counter with toFixed(2), so you create an outside function. But then afterwards you decide that over 5 you want to reset the counter. So you do this:
const counterAsFloat = (counter) => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
This is going to be allowed, will compile and run. It' won't give the expected result, but it won't be obvious. The inside function could work:
const counterAsFloat = () => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
But because in the inside scope counter is a constant you're going to have a compile error or at least a runtime error. That you can quickly fix by replacing counter = 0; by setCounter(0); which is the proper way to handle this requirement.
So in the end, by staying inside your component, it is clearer what the state values are and you're going to have clearer feedback on forbidden manipulations that may be be less obvious with outside functions.
See example with outside function, it is working but doesn't give you the expected result:
const counterAsFloatOutside = (counter) => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
function Counter() {
const [counter, setCounter] = React.useState(0);
return (
<div className="counter">
<h1>{counterAsFloatOutside(counter)}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
ReactDOM.render(React.createElement(Counter, null), document.body);
<script type="text/javascript" src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script type="text/javascript" src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
With the inside function, it's just not working, which in this case, is preferable. Working with any compiling tool will even give you the error upfront, which is a huge advantage:
function Counter() {
const [counter, setCounter] = React.useState(0);
const counterAsFloat = () => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
return (
<div className="counter">
<h1>{counterAsFloat()}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
ReactDOM.render(React.createElement(Counter, null), document.body);
<script type="text/javascript" src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script type="text/javascript" src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>

Categories