UseEffect unable to update useState properly - reactjs

I am trying to update this state that is an empty list on start:
const [winnerList, setWinnerList] = useState([]);
from a useEffect which will run once:
useEffect(()=>{
fetch("/players").then( res => res.json()).then(data => {
if(data) {
console.log(data);
setWinnerList(JSON.parse(data));
console.log(winnerList);
window.localStorage.setItem('winner', JSON.stringify(winnerList));
}
})
},[])
when I console.log (data) I get the json as expected but when I console log(winnerList) I get an empty array even though I setWinnerList with the json data.
after a

This is because setWinnerList is asynchronous. You can change the logic like this :
useEffect(() => {
fetch("/players").then(res => res.json()).then(data => {
if (data) {
console.log(data);
setWinnerList(JSON.parse(data));
}
})
}, []);
useEffect(() => {
console.log(winnerList);
window.localStorage.setItem('winner', JSON.stringify(winnerList));
}, [winnerList]);
First winnerList should be updated, and after the new render has been occured, useEffect callback with winnerList dependency gets called.
Check out react documentation about useEffect and useState and also there are a bunch of very good examples in beta.reactjs about useEffect hook.

Related

Add object array to setState

I'm trying to add an object array in the state series. With this code the useEffect function get stuck in an infinite loop. How can I solve this? Without adding the series const as parameter I get the error about a missing dependency and the code will only run on startup.
import React, { useState, useEffect } from "react";
const LineChart = () => {
const [series, setSeries] = useState([]);
useEffect(() => {
const url = "http://localhost:4000";
const fetchData = async () => {
try {
fetch(url, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
let chartData = data.testRunSummaries
.map(function (testrun) {
return {
duration: testrun.endTime - testrun.startTime,
label: testrun.testSetName + "#" + testrun.userFacingId,
testrun: testrun.testRunId,
status: testrun.status,
};
});
setSeries(chartData, ...series);
console.log(series);
});
} catch (error) {
console.log(error);
}
};
fetchData();
}, [series]);
return (
...
);
};
export default LineChart;
series is in your useEffect dependency array. And your useEffect is changing series. So obviously you'll be stuck in a infinite loop.
You don't need your series to be set as a dependency for useEffect.
As your useEffect will only be trigger once on mount, you can just have
setSeries(chartData);
And if you really need to have former values of series, you should use
setSeries(series => [...chartData, ...series]);
Moreover, seeing your
setSeries(chartData, ...series);
console.log(series);
Let me remind you that setState is async there is no way this will log your updated state :)

useEffect race condition with Redux

Hello I have been struggling to set values when mounting a component. I am using useEffect Hook and useDispatch, useSelector for calling methods and also getting the state from the store. The problem is that the state from the store is delayed 1 render and therefore I need to run two times the code inside useEffect in order to get the behavior I expect. Which is -> when the component loads, do an API call and list some documents.
Data declaration
const data = useSelector(state => state.whole.manufactured);
useEffect code
useEffect(() => {
if (counter <= 1) {
fetchData();
setProducts(data);
setCounter(counter + 1);
}
console.log('data in store', data);
console.log('useEffect');
}, [clickedItem, data]);
fetchData function
const fetchData = async () => {
await dispatch(get_manufacturing());
};
get_manufacturing
return dispatch => {
dispatch(Actions.uiStartLoading());
fetch('http://myapi/api/product/get-products', {
method: 'GET',
headers: Interceptor.getHeaders(),
})
.then(res => res.json())
.then(result => {
dispatch(Actions.uiStopLoading());
if (result.status === true) {
dispatch({type: TYPES.GET_MANUFACTURABLE, data: data});
}
})
.catch(error => {
dispatch(Actions.uiStopLoading());
console.log(error);
});
When this code runs, the following happens.
As you can see in the first render it seems it just completely ignorees the fetchData() and proceeds to the console.log, after the second render the values have been properly set. How can I resolve this issue is there something I'm not getting properly done?
Replace clickedItem in the dependency list with counter.

Array is Updating with useEffect but Always Get the Initial Value

This question has been asked many times but none of the solutions have worked for me. I miss something and I need your help to find it out.
I also provide the whole code to give you context.
I have a TV app that shows the production of a factory.
Initially I request the server to check if statically defined lines are working right now.
let lineIds = ["3", "1", "9"]
TvApp.getInitialProps = async (ctx) => {
try {
const res = await axios.get(`http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`)
const workingLinesResponse = await res
console.log('workingLinesResponse: data: ', workingLinesResponse.data)
return { workingLines: workingLinesResponse.data }
} catch (error) {
console.log('error getInitialProps:: ', error)
return { data: null }
}
}
In the first useEffect, I keep checking every 15 seconds if the lines are still working;
Server responds with the workingLines.
In the second useEffect, I use workingLines to get production data for the currently working lines.
My problem is;
In the first useEffect, setWorkingLines(response.data) is updating workingLines but cannot be used by the second useEffect. Second useEffect always shows the initial value.
export default function TvApp(props) {
const [workingLines, setWorkingLines] = useState(props.workingLines);
//first useEffect
useEffect(() => {
const timer = setInterval(() => {
const API = `http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`
axios.get(API)
.then(response => {
console.log('workingLines initially:: ', response.data)
setWorkingLines(response.data)
})
.catch(error => {
console.log(error.message);
});
return () => clearInterval(timer);
}, 15000);
},[])
var activeLineId = 0
//second useEffect
useEffect(() => {
const timer = setInterval(() => {
axios.get(`http://192.168.1.10:8090/api/tv_app/:${workingLines[activeLineId]}`)//workingLines is always the initial value, not updated one.
.then(response => {
//Set reponse to use in the view.
})
if (activeLineId === workingLines.length - 1) {
activeLineId = 0
} else {
activeLineId = activeLineId + 1
}
return () => clearInterval(timer);
}, 10000);
}, []); //using workingLines as a dependency is causing the multiple api requests
}
If I use workingLines as a dependency in the second useEffect, the api request is calling multiple times.
I can check the workingLines with following useEffect:
useEffect(() => {
console.log('workingLines:: ', workingLines) //this shows that, workingLines is successfully updated by the first useEffect
}, [workingLines])
How can I use the updated workingLines in the second useEffect without calling the api multiple times?

React Hook setting state in useEffect with state in dependency array

I have a question about the correct usage with useEffect and setState in React Hooks.
here is my react app that gets a persisted configuration and some other data depending on the config like this:
function App() {
const [config, setConfig] = useState(null);
// some other states
useEffect(() => {
const fetchData = () => {
axios.get("/path/to/config").then(response => {
if (response.status === 200) {
return response.data
}
})
.then(data => {
setConfig(data.config);
// set some other states
})
.catch(error => {
console.log("Error:" + error);
})
}
fetchData();
}, [config]);
return (
<div >
...
</div>
);
}
If I run this App useEffect is instantly called twice one time for first render and then a second time because setConfig(data.config); is called in the axios get success function.
The user has the possibility to change his own config that is done in another request. If he does I want after state config changes that the config and some other data depending on the config is reloaded via this useEffect function.
Now since there is no setstate callback in react hooks I read somewhere I should use useEffect with the state variable in the dependency array.
How can I prevent that my useEffect is called twice right at the beginning?
I have the feeling that I am doing it all wrong.
Thank you in advance
You need to add an if condition in useEffect, because useEffect is called by default on first render.
useEffect(() => {
if(config !== null){
const fetchData = () => {
axios.get("/path/to/config").then(response => {
if (response.status === 200) {
return response.data
}
})
.then(data => {
setConfig(data.config);
// set some other states
})
.catch(error => {
console.log("Error:" + error);
})
}
fetchData();
}
}, [config]);
This is not right!
I guess this can call fetchData infinitely.

How to setstate after fetch data React hook [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 10 months ago.
Code :
Result : Not have data in state
help me pls , thanks!
setState is asynchronous that's why you are seeing books as empty array. Here is a quote from the React docs:
The setState function is used to update the state. It accepts a new
state value and enqueues a re-render of the component.
One thing you may be doing wrong is in your useEffect callback. If your effect returns a function, React will run it when it is time to clean up. And you don't want the setState functions in fetchData to be invoked during clean up as the component will probably be unmounted.
If you just want the fetchData to only run once after the component mounts, here is a possible solution:
useEffect(() => {
// put the fetchData inside the effect
async function fetchData() {
setLoading(true);
const name = await getNameGroup();
const tmp = await getAll(name);
console.log(tmp);
setBooks(tmp);
console.log(books); // may not be the same as tmp, but you will see the updated state in the next render
setLoading(false);
}
fetchData();
},[]}
You should read more about useEffect hook in the React docs.
It's a stale closure problem.
Your useEffect where the fetchData is being called, has an empty dependency array. Within the fetchData function, which is inside useEffect, you are trying to print books which one first load, was initialized with an empty array.
All hooks hold the same reference to the variables with which they were initialized, till the dependencies change. To get an updated state, they depend on the dependency array. Since your dependency array doesn't specify books, it won't refresh the reference of books in your fetchData function either. Read more about the stale closure problem here
That's why your books variable is showing stale data.
export default function() {
// fetch data here
// runs only once because of empty dependency array
useEffect(() => {
let isCancelled = false
// define the fetchData inside the `useEffect` so that
// you can detect if the component has been unmounted
// using `isCancelled`
const fetchData = async () => {
const tmp = await getAll()
// only update state if component isn't unmounted
// if you try to update state on an unmounted component,
// React will throw an error
if (!isCancelled) {
setIsLoading(false)
setBooks(tmp)
}
}
if (!isCancelled) {
setIsLoading(true)
fetchData()
}
// cleanup
return () => {
isCancelled = true
}
}, [])
}
const [dataArray, setDataArray] = useState([]);
async function fetchData() {
try {
setIsLoading(true);
const response = await getNameGroup();
setDataArray(response);
} catch(error) {
// handle error
} finally {
setIsLoading(false);
}
}
This is an example code that is working and you can apply:
const [data, setData] = useState([]);
const [hasError, setErrors] = useState(false);
async function fetchData() {
const LibraryQuery = JSON.stringify({query: `query { species { id name description } }`});
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const res = await fetch('http://localhost:3000/graphql',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: LibraryQuery
});
res
.json()
.then(res => setData(res.data))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);

Resources