Search code examples
javascriptreact-nativereact-hooksreact-propsasyncstorage

Most recently created object not being saved


I am making a grade calculator app, and I am trying to save my data locally using AsyncStorage so that if the app is refreshed, it should be able to load the previous information that I have on each subject. I have created my save, load, useEffect functions; however, it saves all but the most recent object being created. That means if I create 4 "subjects", it is only saving 3; if I create 5 "subjects", it saves 4; and if I create 1 "subject", it saves 0.

At first, I thought it was because I was saving before I was adding my course, but I do not believe that is the case since I have tried calling save() in addCourse() and nothing changes. I am pretty new to JavaScript, so I would greatly appreciate your help! Here is my code:

Main.js

import React, { useState, useEffect } from 'react';
import {StyleSheet, View, Text, FlatList, TouchableOpacity, Modal,
TouchableWithoutFeedback, Keyboard } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import { globalStyles } from '../styles/global';
import Card from '../shared/card';
import { MaterialIcons } from '@expo/vector-icons';
import ReviewForm from '../screens/reviewForm';

export default function Home({navigation}){
    const [modalOpen, setModalOpen] = useState(false);
    
    const [courses, setCourses] = useState([])

    const addCourse = (course) => {
        course.key = Math.random().toString();
        setCourses((currentCourses) => {
            return[course, ...currentCourses]
        });
        setModalOpen(false);

        console.log('add course')
        console.log(courses)

    }

    const STORAGE_KEY = '@save_course'

    const save = async() => {
        try{
            console.log('running save');
            await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(courses))
        } catch(err){
            alert('error')
            console.log(err)
        }

        // console.log(courses)
    };

    const load = async() => {
        try{
            console.log('running load');
            const jsonSubject = await AsyncStorage.getItem(STORAGE_KEY)
            console.log('load await');
            
            if(jsonSubject != null){
                console.log('entered load if');
                setCourses(JSON.parse(jsonSubject));
            }

        } catch(err){
            alert('error')
            console.log(err)
        }
    };

    useEffect(() => {
        console.log('useEffect');
        load()
    }, [])


    return(
        <View style={globalStyles.container}>
            <Modal visible={modalOpen} animationType='slide'>
                <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
                    <View style={styles.modalContent}>
                        <MaterialIcons 
                            name='arrow-back'
                            style = {{ ...styles.modalToggle, ...styles.modalClose }}
                            size={24}
                            onPress={() => setModalOpen(false)}
                        />
                        <ReviewForm addCourse={addCourse} save={save}/>
                    </View>
                </TouchableWithoutFeedback>
            </Modal>
            <FlatList
                style={{flex: 1, backgroundColor: '#efefef'}}
                data={courses}
                renderItem={({ item }) => (
                    <TouchableOpacity onPress={() => navigation.navigate('CourseDetails', item)}>
                        <Card>
                            <Text style={globalStyles.titleText}>{ item.course }</Text> 
                        </Card>
                    </TouchableOpacity>
                )}
            />
            <MaterialIcons 
                name='add'
                size={28}
                style={styles.modalToggle}
                onPress={() => setModalOpen(true)}
            />
        </View>

    )
}

const styles = StyleSheet.create({
    modalToggle:{
        backgroundColor: 'maroon',
        color: '#efefef',
        width: 75,
        height: 75,
        borderRadius: 50,
        elevation: 6,
        padding: 23,
        alignSelf: 'flex-end',
        position: 'absolute',
        right: 15,
        bottom: 15,
    },
    fabIcon: {
        fontSize: 28,
        color: '#efefef'
    },
    modalClose:{
        left: 10,
        marginTop: 30,
        marginBottom: 10,
        marginRight: 15,
        backgroundColor: '#fff',
        color: '#242424',
        width: 30,
        height: 20,
        borderRadius: 0,
        elevation: 0,
        padding: 0,
        alignSelf: 'flex-start',
        position: 'relative', 
    },  
    modalContent:{
        flex: 1,
    }
});

reviewForm.js

import React from 'react';
import { StyleSheet, TextInput, View, Text } from 'react-native';
import { globalStyles } from '../styles/global.js';
import { Formik } from 'formik';
import * as yup from 'yup';
import FlatButton from '../shared/button';


const courseSchema = yup.object({
    course: yup.string()
        .required()
        .min(3),
    prof: yup.string()
        .min(2),
    // gpa: yup.string()
    //     .test('is-num-1-10', 'GPA must be a number between 1 - 10', (val) => {
    //         return parseInt(val) < 11 && parseInt(val) > 0;
    //     })
})

export default function ReviewForm({ addCourse, save }) {
    return(
        <View style={globalStyles.container}>
            <Formik
                initialValues={{ course: '', prof: '' }}
                validationSchema={courseSchema}
                onSubmit={(values, actions ) => {
                    actions.resetForm();
                    console.log('resetForm');
                    addCourse(values);
                    console.log('values');
                    console.log(values);
                    save();
                    console.log('save');
                    
                }}
            >
                {(props) => (
                    <View>
                        <TextInput 
                            style={globalStyles.input}
                            placeholder='Course Name'
                            onChangeText={props.handleChange('course')}
                            value={props.values.course}
                            onBlur={props.handleBlur('course')}
                        />
                        <Text style={globalStyles.errorText}>{ props.touched.course && props.errors.course }</Text>
                        <TextInput 
                            style={globalStyles.input}
                            placeholder='Professor'
                            onChangeText={props.handleChange('prof')}
                            value={props.values.prof}
                            onBlur={props.handleBlur('prof')}
                        />
                        <Text style={globalStyles.errorText}>{ props.touched.prof && props.errors.prof }</Text>                        
                        <FlatButton text='save' onPress={props.handleSubmit} />
                    </View>
                )}
            </Formik>
        </View>
    )
}

Solution

  • So I figured out the problem as to why my code never added the most recent course. The reason is because setState is asynchronous, and save() is synchronous, so although I called save() after setState, it was performing it before, hence why I didn't save the last one. The way I fixed this was by duplicating my array, and adding the course to the new array, so that I am manually adding in my course before I call save(), so I know all my courses are being saved. Here is the new code...

    const addCourse = (course) => {
            course.key = Math.random().toString();
            newCourses = [course, ...courses]
            setCourses(newCourses);
            save(newCourses);    
            setModalOpen(false);
        }
    

    Hopefully this will help someone in the future if they were to encounter the same problem as me!