This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 1 year ago.
I am using react to switch between light and dark mode in my website . I also want the theme to persist using localstorage . The problem is that while clicking on switch to toggle the theme the corresponding localstorage theme value does not update. I know that state updates are asynchronous but I want to fix this .
My code:
const [darkMode , setDarkMode] = useState(false);
//change theme
const toggleTheme = ()=>{
setDarkMode(!darkMode);
const root = window.document.documentElement;
if(localStorage.getItem("isDark")=== null){
localStorage.setItem("isDark",darkMode);
}else{
localStorage.setItem("isDark",darkMode);
}
root.classList.toggle('dark');
}
I tried using async await but the result was the same .
You can use useEffect in order to keep watch on darkMode.
Here, useEffect will be called every time when darkMode value is updated. Hence, setting the localStorage value inside. And I removed if.. else.. condition as I guess there is no need of that, if you feel you can keep it.
const [darkMode , setDarkMode] = useState(false);
useEffect(() => {
localStorage.setItem("isDark", darkMode);
}, darkMode);
// change theme
const toggleTheme = ()=>{
setDarkMode(!darkMode);
const root = window.document.documentElement;
root.classList.toggle('dark');
}
You can't use state variable darkMode just after updating it via setDarkMode due to its async nature. You could use useEffect hook but that wont be the best way imo just to solve a simple problem like this. Instead you can use it this way
// Initialize variable from localstorage value
const [darkMode, setDarkMode] = useState(
() => JSON.parse(localStorage.getItem("isDark")) ?? false
);
//change theme
const toggleTheme = () => {
const updatedDarkMode = !darkMode;
setDarkMode(updatedDarkMode);
const root = window.document.documentElement;
localStorage.setItem("isDark", updatedDarkMode);
root.classList.toggle("dark");
};
Related
const [businessHour, setBusinessHour] = useState("");
const getHour = () => {
console.log(businessHour);
}
setBusinessHour('1234');
getHour();
The result show "" instead of "1234", any way to update the variable ? Thank you
please update your entire code of a component. The result will show empty string "" first, and then when state is changed, it will print "1234". so there will be multiple logging instead of just one.
I faced the same problem earlier. My workaround is to create a temporary variable to use because the state has not updated yet.
const [businessHour, setBusinessHour] = useState("");
const getHour = (businessHour) => {
console.log(businessHour);
}
let newBusinessHour = '1234'
setBusinessHour(newBusinessHour);
getHour(newBusinessHour);
A better way to do is , use the useEffect callback :
const [businessHour, setBusinessHour] = useState("");
useEffect(() => {
getHour()
},[getHour])
const getHour = useCallback(() => {
console.log(businessHour);
},[businessHour])
setBusinessHour('1234');
so here basically useEffect will be called whenever getHour changes which is dependent on businessHour
Hope it helps.
You can update state if you want to update some views followed by that state.
In the case above, you don't need to update state.
businessHour = '1234';
getHour();
Then you will get the result you want.
I stumbled upon a very interesting question and I would like to know how to best solve this in React. Assume the following code:
const [qrText, setQrText] = useState("")
...
const generateQrCode = () => {
// set other state inside the "then"
QRCode.toDataUrl(qrText).then(...)
}
const handleChange = (e) => {
setQrText(e.target.value)
generateQrCode()
}
This code is unsafe, since state updates are asynchronously, and by the time generateQrCode runs, qrText could still have the old value.
I always tended to solve this problem using a useEffect with dependency array:
const [qrText, setQrText] = useState("")
...
const handleChange = (e) => {
setQrText(e.target.value)
}
useEffect(() => {
const generateQrCode = () => {
// set other state inside the "then"
QRCode.toDataUrl(qrText).then(...)
}
generateQrCode()
}, [qrText])
However, I recently watched a YouTube video from a React conference, where a senior engineer said that useEffect is only supposed to be used to synchronize data with external services or the DOM. Instead, people should update state in event handlers only.
So is this the right way then to handle this scenario?
const [qrText, setQrText] = useState("")
...
// this now takes the qrText as argument
const generateQrCode = (qrTextArg) => {
// set other state inside the "then"
QRCode.toDataUrl(qrTextArg).then(...)
}
const handleChange = (e) => {
const value = e.target.value
setQrText(value)
generateQrCode(value) // pass the event value, instead of relying on the "qrText" state
}
This would equal the "event based" approached, but feels a bit imperative and not "react"-ish.
So I wonder, what is the intended way to do this?
Thanks for your answers!
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
I am trying to change the values of variables within a useEffect call in js React.
I notice that the values get changed inside of the function, but the global value is not changed.
var O_name = "";
var A_name = "";
var A_platform = "";
var C_name = "";
function Statsview( {backButton} ) {
const urlParams = new URLSearchParams(window.location.search);
const name = urlParams.get('name');
var data = useRef();
const [overwatchName, setO_Name] = useState([]);
useEffect(() => {
console.log('mounted')
database.collection('USERS').where('name','==',name).get().then(snapshot => {
snapshot.forEach(doc => {
var data = doc.data()
O_name = data.overwatch;
console.log(O_name)
A_name = data.apexLegends;
console.log(A_name)
A_platform = data.apexLegendsPlatform;
console.log(A_platform)
C_name = data.chess;
console.log(C_name)
})
}).catch(error => console.log(error))
},[]);
console.log("oname: ",O_name)
console.log("aname: ",A_name)
console.log("aplat: ",A_platform)
console.log("cname: ",C_name)
...
...
}
The console logs inside of the useEffect show updated values for each varaible.
However, the console logs outside of the useEffect show that the values for each variable are blank.
How do I change these global values?
Global variables and functions are initiated when React first load all the scripts, that means when you first load (or reload) the page. React itself is not aware of the mutation of these global variables.
Your code flow is like this.
Global variables are initialized.
console.log() prints the content when a component is loaded.
An async call mutates the global variables => React is not aware of them. hence does not re-render your component. Hence, console.log() is not called.
You should use state hook to store these variables so React knows they are changed.
You should use useRef for this case.
so for simple example.
function MyComponent(){
const A_name = useRef('');
useEffect(() => {
A_name.current = 'new value'
}, []);
}
You can change/access the variable with .current property
You can read more at docs here: https://reactjs.org/docs/hooks-reference.html#useref
It mentioned: useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
So it would fit your objective
I've tried almost every solution similar to my problem, yet none is working. I have a simple state and changing the value of this state in a function as like below, handleOnClick is calling in a button's onClick event. I'm also using Router(if it's change something);
import { useState} from "react"
import { BrowserRouter as Router, Route, Link, useHistory} from "react-router-dom";
const Buton = () => {
let x = "";
const [lowerState, setLower] = useState("")
const history = useHistory();
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
setLower(x)
console.log(x) //this prints the current value
console.log(lowerState) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerState})
};
.
.
.
return (...)
}
export default Buton
Now x is a value that returns from an input HTML element. When I set this value as a state and console log, it doesn't print the value first, when I put something in input again, then it prints the first value. So it's like it's coming 1 step behind.
I've used useEffect() , I did put a second parameter to setLower as console.log(lowerState) and other things on the internet that people suggested, but none is working. Every time, the state is coming 1 step behind. How can I make this state changes immediately?
If you want to use the value of an input in a user event function, the best way (and least buggy) is to bind your input value to local state and then just reference that state in your callback function.
Please try to avoid imperatively pulling values from the DOM using getElementById etc. Here's what I mean:
const [value, setValue] = useState('');
// This will keep everything updated until you need to use it
handleChange(event) {
setValue(event.target.value);
}
// Then just grab whatever is in local state
handleClick() {
history.push('/test', {params : value});
}
return (
<input value={value} onChange={handleChange} />
// Your button is here too
)
This is because when you call setLower(x) it is not an async call. So it doesn't wait. That's why you get the 1 step before value in your state right after setting the value.
Official doc - https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
When you call setLower(x), it doesn't immediately update the lowerState. The new value will be available the next time it renders. Because of that the console.log(x) "works" (since it uses the new value that you gain as a parameter) but console.log(lowerState) uses the old value that hasn't updated to the state yet at that point.
If you want history.push('/test', {params : lowerState}) to work, then you need to use the x there instead of lowerState. Or call it within a useEffect with the lowerState and having lowerState as a dependency parameter to the hook.
This is expected behaviour since React is updating state in a batch
Which mean that the state only gets an update after an eventHandler/function is finished
If you want to do some condition, wrap your logic inside a useEffect
useEffect(() => {
if (lowerState === "your-condition-value") {
history.push("/test", { params: lowerState });
}
}, [lowerState]);
Or in your case, just use the variable directly:
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
history.push("/test", { params: x });
};
You should not worry about that since your app still working as expected
So i would like to suggest that use useRef if need for reference only object which may not causing rerendering. also using let x= "" is not correct, you should write code immutable way
const Buton = () => {
const lowerCaseRef = useRef("")
const history = useHistory();
const handleOnClick = () => {
lowerCaseRef.current =
document.querySelector("#my_input").value.toLowerCase();
console.log(lowerCaseRef.current) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerCaseRef.current})
};
return (...)
}
I am using useEffect in react to listen to redux(easy-peasy) state change, but I want to listen to 1st value change only.
Because when my page loads the state has a default value and then API call is made and hence data changes but the API is a polling API, hence it keeps getting the data again and again in a short interval of time. But one of my requirement is to listen only to the 1st API data.
This is what I tried:
1st Approach with empty dependency
useEffect(() => {
// my code
},[])
In this case, I get the default value of my state and not the 1st API response.
2nd Approach with state property in the dependency
useEffect(() => {
// my code
},[myState])
In this case, I keep getting the updated response from the API
both of these approaches didn't work for me. So please suggest a better solution.
You can do so using a ref variable and comparing the state with initial state (which could be null, undefined, empty object depending on your implementation):
const hasRun = useRef(false)
useEffect(() => {
if (!hasRun.current && myState !== initialState) {
hasRun.current = true
// run my code
}
},[myState])
A ref variable won't participate in re-rendering.
What I usually do with this is to have a "previous" state.
You can use this hook for to do that one:
const usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]); // only re-run if value changes
// return previous value (happens before update in useEffect)
return ref.current;
}
You can then do:
// I usually create a distinct initial state
const [var, setVar] = useState(null);
const prevVar = usePrevious(var);
useEffect(() => {
if (var !== prevVar && var !== null) {
// logic here
}
}, [prevVar, var]);
Yers you can simplify this by not using the usePrevious hook, but the hook is quite handy and can be used to check the previous value and the current one.