UseEffect hook executes multiple times on updating state - reactjs

I am updating state of parent component from child component's useEffect hook. Follwing is the piece of code from child component. Here useEffect is getting called twice. Not sure how can I avoid it.
useEffect = () => { const flag = someApi; setStateOfParent(flag),[]}

This below code will fix the issue:
useEffect(() => {
// executed only once
}, [])

You should definitely avoid setting state inside useEffect because it will trigger a new render. Also, if you specify your effect dependencies you will have better control over it and assure that it will only get executed when one of those dependencies change.

You do not set state in useEffect. Make a function to get data from API, call it in useEffect and setState in that function. You can check a an example code below.
useEffect(() => {
getImage();
}, []); // Calling getImage function to fetch data
const [items, set] = useState([]);
async function getImage() {
try {
const response = await axios.get("https://picsum.photos/v2/list");
let image = response.data;
for (var i in image) {
let x = "https://picsum.photos/id/";
let y = image[i].id;
let v = image[i].width;
image[i].author = `${x}${y}/${v}/${image[i].height}`;
image[i].height = Math.floor(Math.random() * 650) + 300;
let z = image[i].height;
image[i].download_url = `url(${x}${y}/${v}/${z})`;
}
set(image); // setting state after fetching data
} catch (error) {
console.error(error);
}
}

Related

useEffect is causing a infinite loop when updating state?

any idea how to refactor this code so I can be able to simply use firestore snapshot (Realtime data) to update the current state with those changeable data the SetState function simply update the current state with the new data but since i am calling is in useEffect it is triggering a loop can I use callback instead to work around this issue ?
const [data, setData] = useState<any>(null);
useEffect(() => {
let colRef = collection(db, 'Weeks');
const docRef = doc(colRef, context.focus as string);
const unsub = onSnapshot(docRef, (doc) => {
setData(doc.data());
if (doc.data()) {
context.SetState({ arr: doc.data()?.arr });
}
return () => unsub;
});
}, [data]);
remove data from the dependency and the infinate loop will end.
you do not use the data in the useEffect, remember to not useEffect on a state that you use its setter inside that useEffect or you will find yourself in such a situation its kinda like a recusive call without a condition to exit the loop.

React Native I can not store an array with AsyncStorage

I am newbie in React Native and I am trying to store and get an array with AsyncStorage in ReactNative.
I have two problems.
First, I do not know why but when I storage data, it only works the second time but I am calling first the set of useState.
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
Second, how can I call the getData function to get all the data and show it? Are there something like .onInit, .onInitialize... in ReactNative? Here is my full code
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
const completeTask = (index) => {
var itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
storeData(taskItems);
}
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#tasks', JSON.stringify(value))
console.log('store', JSON.stringify(taskItems));
} catch (e) {
console.log('error');
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#tasks')
if(value !== null) {
console.log('get', JSON.parse(value));
}
} catch(e) {
console.log('error get');
}
}
Updating state in React is not super intuitive. It's not asynchronous, and can't be awaited. However, it's not done immediately, either - it gets put into a queue which React optimizes according to its own spec.
That's why BYIRINGIRO Emmanuel's answer is correct, and is the easiest way to work with state inside functions. If you have a state update you need to pass to more than one place, set it to a variable inside your function, and use that.
If you need to react to state updates inside your component, use the useEffect hook, and add the state variable to its dependency array. The function in your useEffect will then run whenever the state variable changes.
Even if you're update state setTaskItems([...taskItems, task]) before save new data in local storage, storeData(taskItems) executed before state updated and save old state data.
Refactor handleAddTask as below.
const handleAddTask = () => {
Keyboard.dismiss();
const newTaskItems = [...taskItems, task]
setTaskItems(newTaskItems);
storeData(newTaskItems);
};

Updating state inside useCallback - React JS

How can I restructure this code to allow a state update inside the useCallback function?
Here's what is executed first:
useEffect(() => {
getData();
}, [getData]); // errors if getData is left (missing dependency error)
In the getData function, I pass a state variable (lastDoc) to getSomething() as a parameter. It stores the last document/database row for pagination.
const [lastDoc, setLastDoc] = useState(null);
const getData = useCallback(async() => {
const data = await getSomething(lastDoc);
setLastDoc(data.lastDoc); // useSate function
}, [getSomething, lastDoc]);
This, at the moment, just causes an infinite loop where the getData function is re-rendered once setLastDoc updates the lastDoc variable, as getData has lastDoc as a dependency. If I remove the lastDoc dependency, I get the missing dependency error, which I understand to be an important error to listen to.
I think a null-check might be sufficient.
useEffect(() => {
if(lastDoc === null){
getData();
}
}, [getData, lastDoc]);

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

Not awaiting for data in useEffect

I have a useEffect in my component that is waiting for data from the context so that it can set it in state. But its not waiting for the state and is moving on to the next line of code to set isLoading to false.
I'd like it to wait for the data so that I can render the loading.
I tried setting the isFetchingData in the context but I had run into problems where if another component calls it first it would set the isFetchingData state to false.
First call to ReactContext is setting the isLoading sate to false
It is fine for results to come back with no records. The component would render 'No records found'. Therefore, I cannot check the length on state to say if length is zero then keep loading.
Following is my code:
Context
const [activeEmployees, setActiveEmployees] = useState([]);
const [terminatedEmployees, setTerminatedEmployees] = useState([]);
useEffect(() => {
getEmployees()
.then(response => {
/// some code...
setActiveEmployees(response.activeEmployees)
setTerminatedEmployees(response.terminatedEmployees)
});
});
Component
const EmployeesTab = () => {
const { activeEmployees, terminatedEmployees } = useContext(BlipContext);
//Component states
const [isFetchingData, setIsFetchingData] = useState(true);
const [newEmployees, setNewEmployees] = useState([]);
const [oldEmployees, setOldEmployees] = useState([]);
useEffect(() => {
async function getData() {
await setNewEmployees(activeEmployees);
await setOldEmployees(terminatedEmployees);
setIsFetchingData(false);
}
getData();
}, [activeEmployees, terminatedEmployees, isFetchingData]);
if(isFetchingData) {
return <p>'Loading'</p>;
}
return (
// if data is loaded render this
);
};
export default EmployeesTab;
Since you have useState inside your useContext, I don't see the point of storing yet again the activeEmployees in another state.
If you want to have a local loading variable it could something like:
const loading = !(activeEmployees.length && terminatedEmployees.length);
This loading will update whenever getEmployees changes.
And to answer you question, the reason await is not having an effect is because setState is synchronous.

Resources