How does react hooks handles callback when it sets the state? [duplicate] - reactjs

This question already has answers here:
How To Access Callback Like in setState from useState Hook
(2 answers)
Closed 3 years ago.
In a class-based component when one would set the state, it would take the argument of the new state and sometimes one could fire another function within it. In a functional-based component, using hooks, how can I accomplish the same goal?
// class-based componenet example:
state = {
count: 0
}
this.setState({
count: count +1
}, () => someFunction())
// functional-based componenet example:(how to fire someFunction() when the state is set like in the class-based componenet?)
const [count, setCount] = useState(0)
setCount(count +1)
I'm aware that hooks don't take a second argument but can something similar be done?

I think the useEffect hook would help you here. It basically acts as a componentDidUpdate:
useEffect(() => {
doSomething();
}, [count]);
The second parameter means that the effect / function will only fire if count changes.

Related

useEffect trigger explation [duplicate]

This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
Closed 5 months ago.
Can someone explain why this even triggers the useEffect? The setState function in the useState hook always seems to be a function. Its not undefined (at initialization) at any point in time?
const [state, setState] = useState(0)
useEffect(() => {console.log('triggers')}, [setState])
I'm away of the caveats with React 18s mounting/unmounting, but since setState never changes the effect should not fire? What am I missing here?
The above code is basically the same as writing:
useEffect(() => {...}, [])
It will call on the initial render only
React will call the callback function of useEffect on the initial render and when any of the dependency changes from dependency array.
useEffect(() => {
console.log("triggers");
}, [setState]);
CODESANDBOX DEMO
So, The callback function will trigger on the inital render and when the setState changes (which will never change)
Long story short, useEffect will always run when the component first loads, even if you have a dependency list,
useEffect(()=> { console.log("hello") }) // will log "hello"
useEffect(()=> { console.log("hello") } []) // will log "hello"
useEffect(()=> { console.log("hello") } [x]) // will log "hello"
useEffect(()=> { console.log("hello") } [x, y, z]) // will log "hello"
if you only want your useEffect to fire if the dependency element in the dependency list changes, you need to set some logic inside, for instance, for your case:
const [state, setState] = useState(0)
useEffect(() => {
if(!state) return
console.log('triggers')
}, [state])
and the same if you have something else to check, for instence if you have the initial state set to an empty array, you would do the condition like if(!state.length) return, and so on.
This is kinda sucks when you have multiple elements in the dependency list, that's why the react developers recommend having mutliple useEffects in your component for such case, where each useEffect() handles a piece of logic.

Reactjs: update state not show current value on next line [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 5 months ago.
This is my code:
const [address, setAddress] = useState("1");
const updateData = () => {
setAmount("2");
console.log(address);
}
After updateData, why printed 1? I changed it to 2.
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(address)
// Whatever else we want to do after the state has been updated.
}, [address])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "address" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.

React setState update is happening late ( n-1) , it seems i am lagging by one [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
const handlePageChange = (page, itemCount) => {
setActivePage(page); // This is how am updating my state variable
setItemsPerPage(itemCount); // This is how am updating my state variable
}
handlePageChange is called from onclick() , but it seems like activepage update is happening late ( it seems i am lagging by one) , if I go to 2nd page my state variable of active page will show 1
Since setActivePage is the asynchronous method, you can't get the updated value immediately after setActivePage. You should use another useEffect with dependency to see the updated activePage.
const handlePageChange = (page, itemCount) => {
setActivePage(page);
console.log(activePage); // Old `activePage` value will be printed.
...
}
useEffect(() => {
console.log(activePage) // print state variable inside useEffect.
}, [activePage]);

React Hooks state always one step behind [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I have various functions in React where I need to change the state with useState then do some action conditional on whether the new state meets some criteria.
This uses a setValues method in useState to set the value of newpassword when handleChange is called with prop="newpassword". The new password is then evaluated with a regex test, and if it is valid the state variable passwordIsValid should be set to true.
const handleChange = prop => event => {
setValues({ ...values, [prop]: event.target.value })
if (prop === 'newpassword' && passwordValidation.test(values.newpassword)) {
setValues({ ...values, passwordisValid: true })
console.log(prop, values.passwordisValid)
} else {
console.log(prop, values.passwordisValid)
}
}
The state is always one step behind tho - and I know this is because useState is async, but I don't know how to use useEffect to check the state? Very new to hooks, could someone help me out?
useState() hook is just a function call. It returns value and function pair. values is just a constant it doesn't have any property binding.
// Think of this line
const [values, setValues] = useState(0);
// As these two lines
const values = 0;
const setValues = someFunction;
When you call setValues react updates value for the next render. Next time component renders, it will call useState again which will return new values.
As a solution you should use event.target.value. You don't want to use it directly though because event.target is nullified after you observe it.
const newValue = event.target.value
// use newValue to setValues etc
Inside any particular render, props and state forever stay the same and Every function inside the component render (including event handlers, effects, timeouts or API calls inside them) captures the props and state of the render call that defined it. For that reason if you try to access values.newPassword in your event handler you will always get the state for that particular render i.e the old password.
Just think of useState as a function that returns the state for that particular render and that state is immutable for that particular render.

Can not update state inside setInterval in react hook

I want to update state every second inside setinterval() but it doesn't work.
I am new to react hook so can not understand why this is happening.
Please take a look at the following code snippet and give me advice.
// State definition
const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;
.........................
// call function
React.useEffect(() => {
gameStart();
}, []);
.............
const gameStart = () => {
gameStartInternal = setInterval(() => {
console.log(gamePlayTime); //always prints 100
if (gamePlayTime % targetShowTime === 0) {
//can not get inside here
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
setGamePlayTime(gamePlayTime - 1);
}, 1000);
};
The reason why you did not get updated state is because you called it inside
useEffect(() => {}, []) which is only called just once.
useEffect(() => {}, []) works just like componentDidMount().
When gameStart function is called, gamePlaytime is 100, and inside gameStart, it uses the same value however the timer works and the actual gamePlayTime is changed.
In this case, you should monitor the change of gamePlayTime using useEffect.
...
useEffect(() => {
if (gamePlayTime % targetShowTime === 0) {
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
}, [gamePlayTime]);
const gameStart = () => {
gameStartInternal = setInterval(() => {
setGamePlayTime(t => t-1);
}, 1000);
};
...
You're creating a closure because gameStart() "captures" the value of gamePlayTime once when the useEffect hook runs and never updates after that.
To get around this, you must use the functional update pattern of React hook state updating. Instead of passing a new value directly to setGamePlayTime(), you pass it a function and that function receives the old state value when it executes and returns a new value to update with. e.g.:
setGamePlayTime((oldValue) => {
const someNewValue = oldValue + 1;
return someNewValue;
});
Try this (essentially just wrapping the contents of your setInterval function with a functional state update):
const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;
// call function
React.useEffect(() => {
gameStart();
}, []);
const gameStart = () => {
gameStartInternal = setInterval(() => {
setGamePlayTime((oldGamePlayTime) => {
console.log(oldGamePlayTime); // will print previous gamePlayTime value
if (oldGamePlayTime % targetShowTime === 0) {
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
return oldGamePlayTime - 1;
});
}, 1000);
};
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Dan Abramov article explains well how to work with hooks, state, and the setInterval() type of API!
Dan Abramov! Is one of the React maintaining team! So known and I personally love him!
Quick explanation
The problem is the problem of how to access the state with a useEffect() that executes only once (first render)!
Note: For a deep explanation of why the state isn't updated within the useEffect callback and other inside useEffect callbacks. Check the last section about closures and react re-render ...
The short answer is: by the use of refs (useRef)! And another useEffect() that run again when update is necessary! Or at each render!
Let me explain! And check the Dan Abramov solution! And you'll get better the statement above at the end! With a second example that is not about setInterval()!
=>
useEffect() either run once only, or run in each render! or when the dependency update (when provided)!
Accessing state can be possible only through a useEffect() that run and render each relevant time!
Or through setState((state/*here the state*/) => <newStateExpression>)
But if you want to access the state inside useEffect() => re-run is necessary! meaning passing and executing the new callback!
That doesn't work well with setInterval! If you clear it and re-set it each time! the counter get reset! Leading to no execution if the component is re-rendering fast! And make no sense!
If you render only once! The state is not updated! As the first run, run a one callback! And make a closure! The state is fixed! useEffect(() => { <run once, state will stay the same> setInterval(() => { <state fixed as closure of that time> }) }, []).
For all such kind of situation! We need to use useRef! (refs)!
Save to it a callback that holds the state! From a useEffect() that rerenders each time! Or by saving the state value itself in the ref! Depending on the usage!
Dan abramov solution for setInterval (simple and clean)
That's what you are looking for!
useInteval hook (by Dan Abramov)
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Usage
import React, { useState, useEffect, useRef } from 'react';
function Counter() {
let [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>;
}
We can see how he kept saving the new callback at each re-render! A callback that contains the new state!
To use! it's a clean simple hook! That's a beauty!
Make sure to read Dan article! As he explained and tackled a lot of things!
setState()
Dan Abramov mentioned this in his article!
If we need to set the state! Within a setInteral! One can use simply setState() with the callback version!
useState(() => {
setInterval(() => {
setState((state/*we have the latest state*/) => {
// read and use state
return <newStateExpression>;
})
  }, 1000);
}, []) // run only once
we can even use that! Even when we are not setting a state! Possible! Not good though! We just return the same state value!
setState((state) => {
// Run right away!
// access latest state
return state; // same value (state didn't change)
});
However this will make different react internal code part run (1,2,3), And checks! Which ends by bailing out from re-rendering! Just fun to know!
We use this only when we are updating the state! If not! Then we need to use refs!
Another example: useState() with getter version
To showcase the how to work with refs and state access! Let's go for another example! Here is another pattern! Passing state in callbacks!
import React from 'react';
function useState(defaultVal) {
// getting the state
const [state, setState] = React.useState(defaultValue);
// state holding ref
const stateRef = React.useRef();
stateRef.current = state; // setting directly here!
// Because we need to return things at the end of the hook execution
// not an effect
// getter
function getState() {
// returning the ref (not the state directly)
// So getter can be used any where!
return stateRef.current;
}
return [state, setState, getState];
}
The example is of the same category! But here no effect!
However, we can use the above hook to access the state in the hook simply as below!
const [state, useState, getState] = useState(); // Our version! not react
// ref is already updated by this call
React.useEffect(() => {
setInteval(() => {
const state = getState();
// do what you want with the state!
// it works because of the ref! Get State to return a value to the same ref!
// which is already updated
}, 1000)
}, []); // running only once
For setInterval()! The good solution is Dan Abramov hook! Making a strong custom hook for a thing is a cool thing to do! This second example is more to showcase the usage and importance of refs, in such state access need or problem!
It's simple! We can always make a custom hook! Use refs! And update the state in ref! Or a callback that holds the new state! Depending on usage! We set the ref on the render (directly in the custom hook [the block execute in render()])! Or in a useEffect()! That re-run at each render or depending on the dependencies!
Note about useEffect() and refs setting
To note about useEffect()
useEffect => useEffect runs asynchronously, and after a render is painted on the screen.
You cause a render somehow (change state, or the parent re-renders)
React renders your component (calls it)
The screen is visually updated
THEN useEffect runs
Very important of a thing! useEffect() runs after render() finishes and the screen is visually updated! It runs last! You should be aware!
Generally, however! Effects should be run on useEffect()! And so any custom hook will be ok! As its useEffect() will run after painting and before any other in render useEffect()! If not! As like needing to run something in the render directly! Then you should just pass the state directly! Some people may pass a callback! Imagine some Logic component! And a getState callback was passed to it! Not a good practice!
And if you do something somewhere of some such sense! And talking about ref! Make sure the refs are updated right! And before!
But generally, you'll never have a problem! If you do then it's a smell! the way you are trying to go with is high probably not the right good way!
Closure and more. Why the state variables are not having the latest value?
The why you can't simply access the state value directly boils down to the closure notion. Render function at every re-render go totally with a new call. Each call has its closures. And at every re-render time, the useEffect callback is a new anonymous function. With its new scope of value.
And here the dependency array matter. To access the closure of this call. And so the recent state of this call. You have to make useEffect use the new callback. If dependencies change then that would happen. If not that wouldn't.
And if you do [] then useEffect() would run the first time only. every new call after the first render. Would always have useEffect get a new anonymous function. But none of them is effective or used (run).
The same concept applies to useCallback and many other hooks. And all of that is a result of closures.
callbacks inside the useEffect callback
ex: event listners, setInterval, setTimeout, some(() => {}), api.run(() => {})
Now even if you update the useEffect callback through dependency change. And let's say you did some event listener or setInterval call. But you did it conditionally so if already running no more setting it. The setInterval callback or event listener callback wouldn't get access to the recent state value. Why? you already guessed. They were created in the first run, first call space and all the state values are closure passed down at that **time**. And in the later updates and calls. It's a new render function call. And a whole different useEffect callback as well. Sure it's the same function code. But not same functions at all. The initial callback of the initial run on that render function call. Is a function that was created back then. And then the event listener callback or the setInterval callback was created back then. And they are still in memory and referred to. The event listener part of the event listener API objects instances, and the object that made the registration and the setInterval part of the node runtime. They had the state closure having the value of the time. And never get to see or know about any other re-render call. **Unless somehow you. inject something within. That still referencesorcan access the latest value(references or globals). And those values come from the hooks (useState) and their internal machinery. All it would have is theclosures of the time of the creation.**
Funny metaphor
Most people falling into this trap. Look at the code and saying hey why when the state updates it's not getting the newer value. And the answer is. The state value is something coming from useState, it's not a global variable. And even though you are looking at the same code. The first call and later calls are all different spaces (beings). The only thing making it possible for us to work with functions in this sense is the hooks. And how they store the state and bring it back.
And a good metaphor would be: to get to an office and do some contract and deal. And then later come back and enter the same office, but wait not really the same office rather one that looks the same. But a new office took its place (moved out, moved in, same activity). And you wonder why they didn't recognize you. Yup a bit of an off of a metaphor. But still good a bit.
On the whole I hope that gave you a good sense!
You shouldn't use setInterval with hooks. Take a look at what Dan Abramov, one of the maintainers of React.js, said regarding an alternative on his blog: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

Resources