I'm new to hooks
and recently started using hooks in my React Native projects.
I'm building a simple todo app using the AsyncStorage
. First I initialize initial data
and setData
state using useState
hook:
const [data, setData] = useState([]);
There are two textInput
and submit button that I use to save data to AsyncStorage. Here is the saveData
function:
const saveData = async () => {
const arrData = [{ name: 'vikrant', phone: 123456 }]; // [{ name, phone}] from the textInput
const storedData = await AsyncStorage.getItem('user');
const storedDataParsed = JSON.parse(storedData);
let newData = [];
if (storedData === null) {
// save
await AsyncStorage.setItem('user', JSON.stringify(arrData));
} else {
newData = [...storedDataParsed, user];
await AsyncStorage.setItem('user', JSON.stringify(newData));
}
setName('');
setPhone('');
Keyboard.dismiss();
};
Now, I'm using useEffect
to get data from the AsyncStorage and setting it to the data
state. I'm using data to render the text in the screen.
useEffect(() => {
retrieveData();
}, [data]);
const retrieveData = async () => {
try {
const valueString = await AsyncStorage.getItem('user');
const value = JSON.parse(valueString);
setData(value);
} catch (error) {
console.log(error);
}
};
I'm using [data]
in useEffect since I want to re-render my component each time data changes i.e. each time I save data in AsyncStorage. But this is causing infinite loop as setData
causes useEffect to run infinitely.
If I remove data
from the []
it doesn't loop but my data in render is one step behind. So whenever I save data it doesn't show the current data but the previous one.
Any explanation of what I am doing wrong here and how can i fix this?
Thanks.
Passing no dependencies in a dependency array If your useEffect function does not contain any dependencies, an infinite loop will occur.
It's ok to use setState in useEffect you just need to have attention as described already to not create a loop. The reason why this happen in this example it's because both useEffects run in the same react cycle when you change both prop.
Open the App. js file and start by importing the following components. import React, {useState, useEffect} from 'react'; import { StyleSheet, View, Text, TextInput, TouchableOpacity, } from 'react-native'; import AsyncStorage from '@react-native-community/async-storage'; Next, define a variable name STORAGE_KEY .
If your application is behaving strangely after updating to React 18, the default behavior of useEffect changed to run it 2 times. Just in development mode, but this is the mode everyone builds their application on.
As already mentioned by you, the infinite loop is due to thefact that you pass data
as a dependency to useEffect
and also set in inside the function called in useEffect.
The solution here is to not use useEffect and instead setData whenever you are setting value in AsyncStorage
const saveData = async () => {
const arrData = [{ name: 'vikrant', phone: 123456 }]; // [{ name, phone}] from the textInput
const storedData = await AsyncStorage.getItem('user');
const storedDataParsed = JSON.parse(storedData);
let newData = [];
if (storedData === null) {
// save
await AsyncStorage.setItem('user', JSON.stringify(arrData));
} else {
newData = [...storedDataParsed, user];
await AsyncStorage.setItem('user', JSON.stringify(newData));
}
setName('');
setPhone('');
setData(newData);
Keyboard.dismiss();
};
Just add a conditional flag, retrieve
to wrap async storage, retrieveData()
, calls.
Also in the context of "saving data" I would probably just separate async storage-ish logic with state logic. Current saveData
is polluted with both state and async storage logic.
Something like:
const [retrieve, setRetrieve] = useState(false);
// Pure AsyncStorage context
const saveData = async () => {
...
if (storedData === null) {
await AsyncStorage.setItem('user', JSON.stringify(arrData));
} else {
newData = [...storedDataParsed, user];
await AsyncStorage.setItem('user', JSON.stringify(newData));
}
// XXX: Removed state logic, call it somewhere else.
};
const someHandler = async () => {
await saveData();
setRetrieve(true); // to signal effect to call retrieveData()
}
Then the goal of the effect is just to run retrieveData()
once saving is done.
const [data, setData] = useState([]);
useEffect(() => {
const retrieveData = async () => {
try {
const valueString = await AsyncStorage.getItem('user');
const value = JSON.parse(valueString);
// Other set states
setData(value);
} catch (error) {
console.log(error);
}
};
// Retrieve if has new data
if (retrieve)
retrieveData();
setRetrieve(false);
}
}, [retrieve]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With