0

I try to save the tasks in my ToDo app with AsyncStorage so that they can be retrieved after an app restart.

So far I have managed to save the tasks. However, an empty array is always saved in the first run. If I want to create a new task, it only saves it the second time I click the button. Logical if the whole thing runs asynchronously. I just can't figure out where my fault is. I would be very happy to receive help and tips.

Here you can see the empty array when creating the first task: Reactotron Empty Array

And here you can see the first value get's saved after i created the second task: Reactotron AsyncStorage after second click

First Part:

if (__DEV__) {
  import('./ReactotronConfig').then(() => console.log('Reactotron Configured'));
}
import Reactotron, { asyncStorage } from 'reactotron-react-native';
import React, { useState, useEffect } from 'react';
import {
  Keyboard,
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
  ScrollView,
  Image,
  SafeAreaView,
} from 'react-native';

import AsyncStorage from '@react-native-async-storage/async-storage';
import Task from './components/Task';

export default function App() {
  const [task, setTask] = useState();
  const [taskItems, setTaskItems] = useState([]);

  const getData = async () => {
    try {
      const jsonValue = await AsyncStorage.getItem('TASKS');
      const jsonValue2 = JSON.parse(jsonValue);
      if (jsonValue2 !== null) {
        setTaskItems(jsonValue2);
      }
    } catch (e) {
      alert(e);
    }
  };

  const storeData = async () => {
    await AsyncStorage.clear();
    try {
      const jsonValue = await AsyncStorage.setItem(
        'TASKS',
        JSON.stringify(taskItems)
      );
      return jsonValue;
      Reactotron.log(jsonValue);
    } catch (e) {
      alert(e);
    }
  };

  useEffect(() => {
    getData();
  }, []);

  const handleAddTask = () => {
    storeData();
    Keyboard.dismiss();
    setTaskItems([...taskItems, task]);
    setTask(null);
  };

  const completeTask = (index) => {
    let itemsCopy = [...taskItems];
    itemsCopy.splice(index, 1);
    setTaskItems(itemsCopy);
  };

  const bearyDustLogo = require('./assets/bearydust-logo-bear-with-text.png');

Second Part:

return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        {/* Aufgaben für heute */}

        <View style={styles.tasksWrapper}>
          <View style={styles.headerWrapper}>
            <Text style={styles.sectionTitle}>StandUp Aufgaben</Text>
            <Image style={styles.tinyLogo} source={bearyDustLogo}></Image>
          </View>
          <View style={styles.items}>
            {/* Aufgabenbereich */}
            {taskItems.map((item, index) => {
              return (
                <TouchableOpacity
                  key={index}
                  onPress={() => completeTask(index)}
                >
                  <Task text={item} />
                </TouchableOpacity>
              );
            })}
          </View>
        </View>
      </ScrollView>

      {/* Aufgabe erstellen */}

      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={styles.writeTaskWrapper}
      >
        <TextInput
          style={styles.input}
          placeholder={'Ey! Lass was machen'}
          value={task}
          onChangeText={(text) => setTask(text)}
        />
        <TouchableOpacity
          onPress={() => {
            handleAddTask();
          }}
        >
          <View style={styles.addWrapper}>
            <Text style={styles.addText}>+</Text>
          </View>
        </TouchableOpacity>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}
Henning
  • 3
  • 2

1 Answers1

1

Looking at the code, it's first saving and then it's updating the array, so you will always be one step behind on your storage:

const handleAddTask = () => {
    storeData(); // here you are handling the save
    Keyboard.dismiss();
    setTaskItems([...taskItems, task]); // but you update the values only here
    setTask(null);
  };

To keep your code simple I would suggest you to save each time you have an update on your taskItems but keep in mind you don't need to update the first time you load from storage, so something like this should work:

export default function App() {
  const [task, setTask] = useState();
  const [taskItems, setTaskItems] = useState([]);
  const [loading, setLoading] = useState(true);

  const getData = async () => {
    try {
      const jsonValue = await AsyncStorage.getItem('TASKS');
      const jsonValue2 = JSON.parse(jsonValue);
      if (jsonValue2 !== null) {
        setTaskItems(jsonValue2);
      }
    } catch (e) {
      alert(e);
    } finally {
      setLoading(false)
    }
  };

  const storeData = async () => {
    // commenting this line as you don't need to clean the storage each time you write something on it, as you'll just override the existing key 'TASKS'
    // await AsyncStorage.clear();
    
    // here you validate if the data was loaded already before starting watching for changes on the list
    if (!loading) {
      try {
        const jsonValue = await AsyncStorage.setItem(
          'TASKS',
          JSON.stringify(taskItems)
        );
        return jsonValue;
        Reactotron.log(jsonValue);
      } catch (e) {
        alert(e);
      }
    }
  }

  useEffect(() => {
    getData();
  }, []);

  useEffect(() => {
    storeData()
  }, [taskItems])

  const handleAddTask = () => {
    // won't need this line anymore as now everything you update your list it will be saved.
    // storeData();
    Keyboard.dismiss();
    setTaskItems([...taskItems, task]);
    setTask(null);
  };

  const completeTask = (index) => {
    let itemsCopy = [...taskItems];
    itemsCopy.splice(index, 1);
    // this will trigger a save as well
    setTaskItems(itemsCopy);
  };

  const bearyDustLogo = require('./assets/bearydust-logo-bear-with-text.png');

This way you will always save any updates on the task list and will prevent to save then as empty when first rendering your component.

Success on your project.

  • I love you dude :D you saved my brain from exploding. I had to save your if statement in the useEffect Hook in an asynchronous function and then call it in the useEffect. Otherwise I had a bug that await can only be used in an asynchronous function. Now it saves every entry in the memory and deletes it as soon as I mark the task as completed. You're my hero, thanks alot! – Henning Apr 28 '21 at 07:37
  • Nice to hear it helped! I've updated the code with the async function so anyone can use it as future reference. – Luís C Meireles Apr 29 '21 at 15:58