Search code examples
reactjsreact-nativereact-hooksreact-navigationasyncstorage

Custom React Native Asyncstorage hook causes weird results?


I have a project in React Native that utilizes React Navigation as well as Asyncstorage in order to store data to be edited by child screens. Since we need to listen for changes to each key globally, I made the following Asyncstorage hook that utilizes an EventEmitter:

import AsyncStorage from "@react-native-async-storage/async-storage";
import EventEmitter from "eventemitter3";
import { useEffect, useState } from "react";

const eventEmitter = new EventEmitter();

/**
 * Acts as a react hook for AsyncStorage
 * @param id - ID of the element
 * @param defaultValue - Default value of the element
 * @returns A react hook of the element
 */
export default function useStorage<Type>(id: string, defaultValue: Type): [Type, (value: Type) => Promise<void>] {
    const [version, setVersion] = useState(0);
    const [data, setData] = useState(defaultValue);

    // Grab Data
    const getData = async () => {
        const jsonData = await AsyncStorage.getItem(id);
        if (jsonData)
            setData(JSON.parse(jsonData) as Type);
    };
    useEffect(() => { getData(); }, [version]);

    // Save Data
    const saveData = async (value: Type) => {
        const jsonData = JSON.stringify(value);
        await AsyncStorage.setItem(id, jsonData);
        setData(value);
        eventEmitter.emit(id);
    }
    useEffect(() => {
        let isMounted = true;

        const handleDataChange = () => {
            if (isMounted) {
                setVersion(v => v + 1);
            }
        }
        const unmount = () => {
            isMounted = false;
            eventEmitter.removeListener(id, handleDataChange);
        }

        eventEmitter.addListener(id, handleDataChange);
        return unmount;
    }, [id]);

    return [data, saveData];
}

However, whenever I utilize it, I get very strange results including...

  • Data not properly loading when the same key is on parent and child screen simultaneously
  • Data not properly updating
  • Hook not cleaning up properly

Am I missing something? What is causing all of these issues? Is there a better way of implementing this?

Note: I am aware React Redux exists, but I would prefer to avoid it if possible.


Solution

  • Calling multiple setStates() in a single function can cause unintended side effects. By adjusting the code to only use 1 setState() per function, these issues can be avoided.