React Native Asyncstorage / useReducer returns null value

Viewed 128

Anybody has experience in AsyncStorage in React Native? It returns wired values something like this.

"_U": 0, 
"_V": 1,
"_X": null,
"_W": {}

And here is Context, useReducer hook code.

const [localState, localDispatch] = useReducer(
    local,
    localInitialState,
    async () => {
      await AsyncStorage.removeItem(‘local’);
      const storedLocalData = await AsyncStorage.getItem(‘local’);
      console.log(‘LOCAL: ’, storedLocalData);
      storedLocalData ? console.log(‘LOCAL-YES’) : console.log(‘LOCAL-NO’);
      return storedLocalData ? JSON.parse(storedLocalData) : localInitialState;
    },
  );
const [themeState, themeDispatch] = useReducer(
    themeReducer,
    themeInitialState,
    async () => {
      await AsyncStorage.removeItem(‘theme’);
      const storedThemeData = await AsyncStorage.getItem(‘theme’);
      console.log(‘THEME: ’, storedThemeData);
      storedThemeData ? console.log(‘THEME-YES’) : console.log(‘THEME-NO’);
      return storedThemeData ? JSON.parse(storedThemeData) : themeInitialState;
    },
  );

Local state works well but theme sate which copied from local does not work... enter image description here

And this is Console state. Local state already stored in Asyncstorage. but Theme state returns null.. with the same code.. the State should be works like local state. not the theme state. I hope any advise, Thanks.

1 Answers

Unfortunately there's no possibility for useReducer to have a function that returns a Promise as initializer for now! (which I think it's necessary for the next updates of React)

but here's my solution for now: (written in typescript)

import React from "react";
import { CommonActionTypes } from "context/common/CommonActions";
import useStorage from "./useStorage";

/**
 * --- IMPORTANT ----
 * if you're using this wrapper, your reducer must handle the ReplaceStateAction
 * **Also** your state needs to have a property named `isPersistedDataReady` with `false` as default value
 */

export function usePersistedReducer<State, Action>(
  reducer: (state: State, action: Action) => State,
  initialState: State,
  storageKey: string,
): [State, React.Dispatch<Action>] {
  const { value, setValue, isReady } = useStorage<State>(storageKey, initialState);

  const reducerLocalStorage = React.useCallback(
    (state: State, action: Action): State => {
      const newState = reducer(state, action);
      setValue(newState);
      return newState;
    },
    [value],
  );

  const [store, dispatch] = React.useReducer(reducerLocalStorage, value);

  React.useEffect(() => {
    isReady &&
      // @ts-ignore here we need an extension of union type for Action
      dispatch({
        type: CommonActionTypes.ReplaceState,
        state: { ...value, isPersistedDataReady: true },
      });
  }, [isReady]);

  return [store, dispatch];
}

then in your views isPersistedDataReady value.

here's also the implementation of the hook useStorage

import AsyncStorage from "@react-native-async-storage/async-storage";

const useStorage = <T>(key: string, defaultValue: T) => {
  type State = { value: T; isReady: boolean };

  const [state, setState] = React.useState<State>({
    value: defaultValue,
    isReady: false,
  });

  React.useEffect(() => {
    get()
      .then((value) => {
        setState({ value, isReady: true });
      })
      .catch(() => {
        setState({ value: defaultValue, isReady: true });
      });
  }, []);

  React.useEffect(() => {
    state.value && state.isReady && save(state.value);
  }, [state.value]);

  const setValue = (value: T) => {
    setState({ value, isReady: true });
  };

  const save = (value: T): Promise<void> => {
    if (value) {
      try {
        const savingValue = JSON.stringify(value);
        return AsyncStorage.setItem(key, savingValue);
      } catch (er) {
        return Promise.reject(er);
      }
    } else {
      return Promise.reject(Error("No value provided"));
    }
  };
  const get = (): Promise<T> => {
    return AsyncStorage.getItem(key, () => defaultValue).then((value) => {
      if (value === null) {
        throw Error(`no value exsits for ${key} key in the storage`);
      }
      return JSON.parse(value);
    });
  };
  const remove = (): Promise<void> => {
    return AsyncStorage.removeItem(key);
  };

  return { ...state, setValue, clear: remove };
};

export default useStorage;

Related