Cannot change variable in useEffect in React [duplicate] - javascript

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

Related

React state setter function not changing the state

I am working on a history component in my React project. I have the history state and a setter function setHistory() . I have two functions for handling clicks: handleClick() and removeHistoryItem(), which are called with different buttons. In both of them, I am changing the state with the setHistory() function, but it doesn't work in the handleClick() one.
const removeHistoryItem = () => {
const newHistory = history.filter(item => item.id != id)
setHistory(newHistory)
}
const handleClick = () => {
let newHistory = history
let [selectedGame] = history.filter(item => item.id == id)
const index = history.indexOf(selectedGame)
selectedGame.isFavorite = !selectedGame.isFavorite
newHistory.splice(index, 1, selectedGame)
localStorage.setItem('history', JSON.stringify(newHistory))
setHistory(newHistory)
}
The functions are next to each other so there shouldn't be a problem with the scope. Both functions are executing the other operations well (tested with console.log(), the localStorage is changed just fine, the splice and filter functions are working also). Even the value I am passing to the setHistory() function is correct, just the state is not changed. It's like the setHistory() function is not even called. I am not getting any errors, what could the problem be?
Error is due to Reference equality, both newHistory and history in your handleClick function are referencing same array in "memory", which you mutate with splice, but it is still the same object, react will not detect the change and will not fire the rerender, just because oldValue (history) === newValue (newHistory).
So you need to "recreate" the array in this case. For removeHistoryItem all is ok due to .filter returns the new array already, so new reference, new object.
setHistory([...newHistory]);

problem handling promise in useState React

After selecting two values ​​from a list, I must make a call to an api that returns a Json with the information
In this way I call the api that responds with the JSON
export default class ProductsService {
async getProducts(channel, category){
const aux = await axios.post('https://appshop.dapducasse.cl/ducasse-api/api/Qrs?channel='+encodeURIComponent(channel)+'&category='+encodeURIComponent(category)).then(data =>data.data).catch(e => [])
return aux
}
}
In this way I make the call from the react component
const chanelService = new ChanelService();
const categoryService = new CategoryService();
const productsService = new ProductsService();
useEffect(() => {
chanelService.getChanels().then(data => setChanels(data))
categoryService.getCategories().then(data => setCateries(data))
}, []);
const testChargue = async ( ) =>{
console.log(selectedChannel)
console.log(selectedCategory)
const a = await productsService.getProducts(selectedChannel, selectedCategory).then(data => setProducts(data));
console.log(products)
}
When I press the button, the function should be executed where the call is made with the Channel and the selected product.
I don't get the json on the first click of the button, but on the second, I think this happens because the execution ends before setProducts defines the state of products.
I assume products and setProducts refer to local state in your component?
In this case products in the last line will always be the "old" value inside your handler function, because setProducts will not update that variable. The variable will only changed when the component will been re-rendered, but not during the execution of your handler function. I think that's why you see the value when pressing the button twice. On the second click the component has been re-rendered, so the value also in your handler function has been updated.
More on "stale" state in the react docs: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function

React state updating but rendering late

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 (...)
}

react does not update localstorage value based on state [duplicate]

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");
};

How to fix scope problem inside "then" function react?

I am trying to print out of Printer. I am a little new to react and javascript. I am trying to pass the state to a then function of Third Party Code. But i am getting an error:
Cannot read property 'restaurant_name' of undefined
How can i pass state to the scope of then function of qz?
print = () => {
let { state } = this.state;
qz.websocket.connect()
.then(function() {
return qz.printers.find("BillPrinter");
}).then(function(printer) {
var config = qz.configs.create(printer);
var data = [
`${state.restaurant_name}` + '\x0A',
`${state.restaurant_address}`
]
return qz.print(config, data);
});
}
You have some unnecessary destructuring that is causing your error - this.state.state doesn't exist, yet this line:
let { state } = this.state;
Is equivalent to:
let state = this.state.state;
Remove the curly braces and it'll work fine.
let state = this.state;
Also note that state will be a reference to this.state rather than being another object.
Use arrow function to keep the function in the upper scope as #Ali Torki suggested:
.then(printer => {....})

Categories