setState not working inside useEffect hook - reactjs

I fetched some data from my firebase realtime database which is returned as an object inside my useEffect hook function. I wanted to map these data into different rows in my UI. But the problem is when I try to setState in the component by passing the fetched data in the setState() function, it returns an empty object. I tried to convert the fetched data object to convert to an array and then set the state, still, the console.log(state) shows an empty array. When I use the state as a dependency (2nd argument in the useEffect) it returns an infinite loop of the state. The code looks like this-
const ToDoList = () => {
const [toDo, setToDo] = useState([])
useEffect(() => {
const fetchData = async () => {
try{
toDoListRef.on('value', snapshot => {
const fetchedPostObject = snapshot.val()
console.log(fetchedPostObject) // shows fetched data in the object form
var fetchedPostArr = []
fetchedPostArr = Object.entries(fetchedPostObject)
setToDo(fetchedPostArr)
console.log(toDo) // []
})
}catch(err){
console.log(err)
}
}
fetchData()
}, [])
}
if I don't convert the fetched object to an array then it returns an empty object if again I use the dependency, it returns an infinite state.

You will not get the updated value just after the setState as it is async. Instead, you can try something like this
useEffect (() => {console.log(toDo)},[toDo])
This hook listens to any updated value of toDo and will execute when the value of toDo gets updated.

Related

usestate not setting the data

i am using usestate for transfer data. but ufotunately it not quite work.
here is my code:
const [totCons, settotCons] = useState(null)
useEffect(() => {
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch('https://piscons2.vercel.app/ConsPiscTotCons');
// convert the data to json
const json = await data.json();
// set state with the result
settotCons(json);
console.log(json)
console.log(totCons)
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);;
}, [])
as you can see on image the json return data but the totCons return null.
i did set it settotCons(json)
Updated state will not be available to the state value immedieately.
The react setState is asynchronous, but thats not the only reason for this behaviour. The reason is a closure scope around an immutable const value.
Both props and state are assumed to be unchanging during 1 render.
Treat this.state as if it were immutable.
You can use useEffect to create the sideeffects for totCons
useEffect(() => {
// action on update of totCons
}, [totCons]);
try doing console.log(totCons) outside useEffect.
you will not get the updated value in next line.
you will get the updated value in next render

React: Axios.get -> setState(response) -> use state data immediately in another function - how?

I have made a functional component, instead of class (too late to change, I've written almost 1000 lines already), and I have a big problem with using data in a function, because the state is not updated when the function is called, so I'm getting an empty array... I'm not sure what can be done here, so that the function doStuffWithIt() is able to use updated states that are not empty.
So basically this is how I have it setup:
const [objects, setObjects] = useState([])
const [processedObjects, setProcessedObjects] = useState([])
async function loadData() {
await axios.all([
axios.get("/api/objects"),
...etc //multiple other axios.get calls
]).then(axios.spread((...responses) => {
setObjects(responses[0].data)
...etc //multiple setStates
})).catch(errors => {
console.log(errors)
}
useEffect(() => {
loadData()
doStuffWithIt()
}, [])
function doStuffWithIt() {
console.log(objects) // <-- returns empty array []
//process the array with .map function and push the changes into processedObjects with setProcessedObjects,
//so I can use processed objects as options inside a react-select component
}
return (
<Select options={processedObjects} />
)
Note: I will have multiple components like this, that will need to have the fetched data to be processed and set in another state hook.
Just put the code of that function inside effect with needed dependency.
useEffect(()=>{
console.log(objects) // <-- returns empty array []
//process the array with .map function and push the changes into processedObjects with setProcessedObjects,
//so I can use processed objects as options inside a react-select component
}, [objects])

Update array using useState

I'm calling an API inside the useEffect hook and trying to update my state covidData but my array remains empty even after calling the setData function:
const [covidData, setData] = useState([]);
useEffect(() => {
async function getGlobalData() {
let response = await fetch('https://api.covid19api.com/summary');
let jsonResponse = await response.json();
const globalData = jsonResponse.Global;
//setting covidData
setData([...covidData, {recovered: globalData.TotalRecovered}])
console.log(covidData); //covidData is empty
}
getGlobalData()
}, [])
what am I doing wrong?
Your code is correct, the state is updated asynchronously, it's normal your console.log will not display your state after your setState.
It is how react works, when you change state of something, react creates new instance of virtual dom.
So when you change state of covidData the value you set will be in new instance and the console.log is still in that old instance so it logs old value which is empty as you set when using useState.
Try logging with button onClick event and you will see your data or you can check with React Dev Tools
Also you can refactor your code as
useEffect(async () => {
let response = await fetch('https://api.covid19api.com/summary');
let jsonResponse = await response.json();
const globalData = jsonResponse.Global;
//setting covidData
setData([...covidData, {recovered: globalData.TotalRecovered}])
}, [])
More on virtual dom
React Docs
What is virtual DOM

React functional component access state in useeffect

I've gote some react component like below. I can use "messages" in return, but if I try to access messages inside some function, or useEffect, as in example, I always become initial value. How can I solve it in functional component? Thanks
const Messages = () => {
const { websocket } = useContext(WebsocketsContext);
let [ messages, setMessages ] = useState([]);
useEffect(() => {
getMessages()
.then(result => {
setMessages(result);
})
}, []);
useEffect(() => {
if(websocket != null){
websocket.onmessage = (msg) => {
let wsData = JSON.parse(msg.data);
if(wsData.message_type == 'Refresh'){
console.log(messages)
};
};
};
}, [websocket]);
return(
<div>...</div>
);
};
export default Messages;
Looks like you have encountered a stale closure
the useEffect with [websockets] in its dependency array will only ever "update" whenever the websocket reference/value changes. Whenever it does, the function will have created a "closure" around messages at that point in time. Thus, the value of messages will stay as is within that closure. If messages updates after websocket has been created, it will never update the value of "messages" within the onmessage callback function.
To fix this, add "messages" to the dependency array. [websockets, messages]. This will ensure the useEffect callback always has the latest state of messages, and this the onmessage function will have the latest state of messages.
useEffect(() => {
if(websocket != null){
websocket.onmessage = (msg) => {
let wsData = JSON.parse(msg.data);
if(wsData.message_type == 'Refresh'){
console.log(messages)
};
};
};
}, [websocket, messages]);
It's because your getMessages() is an async function. The order is as follows: component mounts initially and values are initialized -> componentDidMount() is invoked meaning your getMessages() is invoked (an async function!) -> your webaocket is initialized and invokes the second useEffect, which reads the initial value of messages -> your getMessages gets its response and sets the messages accordingly.
To make it work as intended, make the second useEffect's dependency array as [websocket, messages].

Prevent infinite renders when updating state variable inside useEffect hook with data fetched using useQuery of graphql

Graphql provides useQuery hook to fetch data. It will get called whenever the component re-renders.
//mocking useQuery hook of graphql, which updates the data variable
const data = useQuery(false);
I am using useEffect hook to control how many times should "useQuery" be called.
What I want to do is whenever I receive the data from useQuery, I want to perform some operation on the data and set it to another state variable "stateOfValue" which is a nested object data. So this has to be done inside the useEffect hook.
Hence I need to add my stateOfValue and "data" (this has my API data) variable as a dependencies to the useEffect hook.
const [stateOfValue, setStateOfValue] = useState({
name: "jack",
options: []
});
const someOperation = (currentState) => {
return {
...currentState,
options: [1, 2, 3]
};
}
useEffect(() => {
if (data) {
let newValue = someOperation(stateOfValue);
setStateOfValue(newValue);
}
}, [data, stateOfValue]);
Basically I am adding all the variables which are being used inside my useEffect as a dependency because that is the right way to do according to Dan Abramov.
Now, according to react, state updates must be done without mutations to I am creating a new object every time I need to update the state. But with setting a new state variable object, my component gets re-rendered, causing an infinite renders.
How to go about implementing it in such a manner that I pass in all the variables to my dependency array of useEffect, and having it execute useEffect only once.
Please note: it works if I don't add stateOfValue variable to dependencies, but that would be lying to react.
Here is the reproduced link.
I think you misunderstood
what you want to be in dependencies array is [data, setStateOfValue] not [data, stateOfValue]. because you use setStateOfValue not stateOfValue inside useEffect
The proper one is:
const [stateOfValue, setStateOfValue] = useState({
name: "jack",
options: []
});
const someOperation = useCallback((prevValue) => {
return {
...prevValue,
options: [1, 2, 3]
};
},[])
useEffect(() => {
if (data) {
setStateOfValue(prevValue => {
let newValue = someOperation(prevValue);
return newValue
});
}
}, [data, setStateOfValue,someOperation]);
If you want to set state in an effect you can do the following:
const data = useQuery(query);
const [stateOfValue, setStateOfValue] = useState({});
const someOperation = useCallback(
() =>
setStateOfValue((current) => ({ ...current, data })),
[data]
);
useEffect(() => someOperation(), [someOperation]);
Every time data changes the function SomeOperation is re created and causes the effect to run. At some point data is loaded or there is an error and data is not re created again causing someOperation not to be created again and the effect not to run again.
First I'd question if you need to store stateOfValue as state. If not (eg it won't be edited by anything else) you could potentially use the useMemo hook instead
const myComputedValue = useMemo(() => someOperation(data), [data]);
Now myComputedValue will be the result of someOperation, but it will only re-run when data changes
If it's necessary to store it as state you might be able to use the onCompleted option in useQuery
const data = useQuery(query, {
onCompleted: response => {
let newValue = someOperation();
setStateOfValue(newValue);
}
)

Resources