Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting state along with AsyncStorage in useEffect hook causes infinite loop?

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.

like image 506
vikrantnegi Avatar asked Feb 20 '20 04:02

vikrantnegi


People also ask

What causes infinite loop in useEffect?

Passing no dependencies in a dependency array If your useEffect function does not contain any dependencies, an infinite loop will occur.

Can I set state inside a useEffect hook?

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.

How do you use AsyncStorage in useEffect in react native?

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 .

Why does useEffect run multiple times?

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.


Video Answer


2 Answers

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();
};
like image 166
Shubham Khatri Avatar answered Oct 23 '22 20:10

Shubham Khatri


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]);
like image 41
Joseph D. Avatar answered Oct 23 '22 22:10

Joseph D.