Search code examples
reactjsreact-nativestatespread-syntax

React Native how can I get last value of stateful array which has been updated by a spread operator?


I am updating a list (kind of like a todo list) and trying to persist it to AsyncStorage but the latest item added to array is always missing. Why?

Here is the offending function (shortened for clarification):

// At beginning of component
let [itemsArray, updateItemsArray] = useState([])

const addItem = async (item) => {

    const currentItem = {
        id: uuid(), // <-- temporary way of getting key for now
        name: item.name
    }

    // Use spread operator to update stateful array for screen listing
    // The listing on the screen updates perfectly with the 'new item' in place at the bottom 
    of a list
    updateJobsArray(prevItems => [...prevItems, currentJob])

    // Now, stringify the items array in preparation for saving to AsyncStorage
    updateItemsArray(prevItems => [...prevItems, currentItem])
    try {
        const jsonValue = JSON.stringify(itemsArray)
        await AsyncStorage.setItem('items', jsonValue)
    } catch (e) {
        Alert.alert('Error', 'Something went horribly, irrevocably... wrong')
    }
}

When I console.log AsyncStorage.getItem('items'), the last item added is always missing from the resultant list of items. The items list is always missing the last added item. I think that the problem lies in the way the spread operator updates the stateful 'itemsArray'. It's like as if the state update is async and the write to AsyncStorage happens before the update is finished, but I can't find out why, please help...


Solution

  • I reproduce issue with working example, please test code at https://snack.expo.dev/@emmbyiringiro/c65dbb

    import * as React from 'react';
    import { Text, View, StyleSheet,Button,AsyncStorage,Alert,ScrollView } from 'react-native';
    import Constants from 'expo-constants';
    import faker from 'faker'
    // You can import from local files
    import AssetExample from './components/AssetExample';
    
    // or any pure javascript modules available in npm
    import { Card } from 'react-native-paper';
    
    export default function App() {
    
      let [itemsArray, updateItemsArray] = React.useState([])
        let [savedUsers, updateSavedUsers] = React.useState([])
    
        let [asyncOps,updateAsyncOps] = React.useState({saveStatus:"undetermined",retrieveStatus:"undetermined"})
    
    
    
    const save = async ()=>{
    
    const  newUser ={
       id:faker.datatype.uuid()
       ,
     name: faker.name.findName(), // Rowan Nikolaus
       email: faker.internet.email(),// [email protected],
       phone:faker.phone.phoneNumber(),
      
    }
    
    const tempUsers = [...itemsArray,newUser]
    
    const serializeValues = JSON.stringify(itemsArray)
    
    try {
    
    
      updateAsyncOps({...asyncOps,saveStatus:"pending"})
           
          await AsyncStorage.setItem('users', serializeValues)
           await retrieve()
          updateItemsArray(tempUsers)
         
           updateAsyncOps({...asyncOps,saveStatus:"succeeded"})
        } catch (e) {
          updateAsyncOps({...asyncOps,saveStatus:"failed"})
            Alert.alert('Error', 'Something went horribly, irrevocably... wrong')
        }
    
    
    
      }
    
    
      const retrieve = async () => {
      try {
         updateAsyncOps({...asyncOps,retrieveStatus:"pending"})
        const value = await AsyncStorage.getItem('users');
        if (value !== null) {
          // We have data!!
          console.log(value);
          const deSerializeValue = JSON.parse(value)
    
          updateSavedUsers( deSerializeValue)
           updateAsyncOps({...asyncOps,retrieveStatus:"suceeded"})
        }
      } catch (error) {
        // Error retrieving data
      Alert.alert('Error', 'Something went horribly, irrevocably... wrong')
          updateAsyncOps({...asyncOps,retrieveStatus:"failed"})
      }
    };
      return (
        <ScrollView style={styles.container}>
         
          <Card>
    
          <View>
    
          { savedUsers.map(user=>{
    
    
            return (
    
              <View style={{paddingVertical:5}} key={user.id}>
    
    <Text> { user.name} </Text>
    <Text> { user.email} </Text>
    <Text> { user.phone} </Text>
              </View>
            )
          })}
    
          </View>
          <View style={{padding:10}}>
          <Button onPress={save} title ='Add User '  disabled={asyncOps.saveStatus === 'pending'}/>
          <View style={{paddingVertical:10}}> 
           <Button  onPress={retrieve} title ='Retrieve Users ' disabled={asyncOps.retrieveStatus === 'pending'}/>
           </View>
           </View>
          </Card>
          
        </ScrollView>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        paddingTop: Constants.statusBarHeight,
        backgroundColor: '#ecf0f1',
        padding: 8,
      },
      paragraph: {
        margin: 24,
        fontSize: 18,
        fontWeight: 'bold',
        textAlign: 'center',
      },
    });