Repository: alibaba/hooks
Pull request #770 (2020-12-04 09:17:36)

refactor: useStorageState -> createUseStorageState


Patch:
diff --git a/packages/hooks/src/createUseStorageState/index.ts b/packages/hooks/src/createUseStorageState/index.ts
new file mode 100644
index 00000000..051a4f52
--- /dev/null
+++ b/packages/hooks/src/createUseStorageState/index.ts
@@ -0,0 +1,71 @@
+import { useState, useCallback } from 'react';
+import useUpdateEffect from '../useUpdateEffect';
+
+export interface IFuncUpdater<T> {
+  (previousState?: T): T;
+}
+
+export interface IFuncStorage {
+  (): Storage;
+}
+
+export type StorageStateResult<T> = [T | undefined, (value?: T | IFuncUpdater<T>) => void];
+
+function isFunction<T>(obj: any): obj is T {
+  return typeof obj === 'function';
+}
+
+export function createUseStorageState(nullishStorage: Storage | null) {
+  function useStorageState<T>(
+    key: string,
+    defaultValue?: T | IFuncUpdater<T>,
+  ): StorageStateResult<T> {
+    const storage = nullishStorage as Storage;
+    const [state, setState] = useState<T | undefined>(() => getStoredValue());
+    useUpdateEffect(() => {
+      setState(getStoredValue());
+    }, [key]);
+
+    function getStoredValue() {
+      const raw = storage.getItem(key);
+      if (raw) {
+        try {
+          return JSON.parse(raw);
+        } catch (e) {}
+      }
+      if (isFunction<IFuncUpdater<T>>(defaultValue)) {
+        return defaultValue();
+      }
+      return defaultValue;
+    }
+
+    const updateState = useCallback(
+      (value?: T | IFuncUpdater<T>) => {
+        if (typeof value === 'undefined') {
+          storage.removeItem(key);
+          setState(undefined);
+        } else if (isFunction<IFuncUpdater<T>>(value)) {
+          const previousState = getStoredValue();
+          const currentState = value(previousState);
+          storage.setItem(key, JSON.stringify(currentState));
+          setState(currentState);
+        } else {
+          storage.setItem(key, JSON.stringify(value));
+          setState(value);
+        }
+      },
+      [key],
+    );
+
+    return [state, updateState];
+  }
+  if (!nullishStorage) {
+    return function (defaultValue: any) {
+      return [
+        isFunction<IFuncUpdater<any>>(defaultValue) ? defaultValue() : defaultValue,
+        () => {},
+      ];
+    } as typeof useStorageState;
+  }
+  return useStorageState;
+}
diff --git a/packages/hooks/src/useLocalStorageState/index.ts b/packages/hooks/src/useLocalStorageState/index.ts
index aaf47547..ad57392c 100644
--- a/packages/hooks/src/useLocalStorageState/index.ts
+++ b/packages/hooks/src/useLocalStorageState/index.ts
@@ -1,16 +1,7 @@
-import useStorageState from '../useStorageState';
+import { createUseStorageState } from '../createUseStorageState';
 
-function useLocalStorageState<T = undefined>(
-  key: string,
-): [T | undefined, (value?: T | ((previousState?: T) => T)) => void];
-
-function useLocalStorageState<T>(
-  key: string,
-  defaultValue: T | (() => T),
-): [T, (value?: T | ((previousState: T) => T)) => void];
-
-function useLocalStorageState<T>(key: string, defaultValue?: T | (() => T)) {
-  return useStorageState(() => localStorage, key, defaultValue);
-}
+const useLocalStorageState = createUseStorageState(
+  typeof window === 'object' ? window.localStorage : null,
+);
 
 export default useLocalStorageState;
diff --git a/packages/hooks/src/useSessionStorageState/index.ts b/packages/hooks/src/useSessionStorageState/index.ts
index b8505984..57b97f5b 100644
--- a/packages/hooks/src/useSessionStorageState/index.ts
+++ b/packages/hooks/src/useSessionStorageState/index.ts
@@ -1,16 +1,7 @@
-import useStorageState from '../useStorageState';
+import { createUseStorageState } from '../createUseStorageState';
 
-function useSessionStorageState<T = undefined>(
-  key: string,
-): [T | undefined, (value?: T | ((previousState?: T) => T)) => void];
-
-function useSessionStorageState<T>(
-  key: string,
-  defaultValue: T | (() => T),
-): [T, (value?: T | ((previousState: T) => T)) => void];
-
-function useSessionStorageState<T>(key: string, defaultValue?: T | (() => T)) {
-  return useStorageState(() => sessionStorage, key, defaultValue);
-}
+const useSessionStorageState = createUseStorageState(
+  typeof window === 'object' ? window.sessionStorage : null,
+);
 
 export default useSessionStorageState;
diff --git a/packages/hooks/src/useStorageState/index.ts b/packages/hooks/src/useStorageState/index.ts
deleted file mode 100644
index ef74c2f7..00000000
--- a/packages/hooks/src/useStorageState/index.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useState } from 'react';
-import useUpdateEffect from '../useUpdateEffect';
-
-export interface IFuncUpdater<T> {
-  (previousState?: T): T;
-}
-
-export interface IFuncStorage {
-  (): Storage;
-}
-
-export type StorageStateDefaultValue<T> = T | IFuncUpdater<T>;
-
-export type StorageStateResult<T> = [T | undefined, (value: StorageStateDefaultValue<T>) => void];
-
-function isFunction<T>(obj: any): obj is T {
-  return typeof obj === 'function';
-}
-
-function useStorageState<T>(
-  storage: Storage | IFuncStorage,
-  key: string,
-  defaultValue?: StorageStateDefaultValue<T>,
-): StorageStateResult<T> {
-  if (typeof window !== 'object')
-    return [isFunction<IFuncUpdater<T>>(defaultValue) ? defaultValue() : defaultValue, () => {}];
-
-  const [state, setState] = useState<T | undefined>(() => getStoredValue());
-  useUpdateEffect(() => {
-    setState(getStoredValue());
-  }, [key]);
-
-  function getStoredValue() {
-    if (isFunction<IFuncStorage>(storage)) {
-      storage = storage();
-    }
-    const raw = storage.getItem(key);
-    if (raw) {
-      try {
-        return JSON.parse(raw);
-      } catch (e) {}
-    }
-    if (isFunction<IFuncUpdater<T>>(defaultValue)) {
-      return defaultValue();
-    }
-    return defaultValue;
-  }
-
-  function updateState(value?: T | IFuncUpdater<T>) {
-    if (isFunction<IFuncStorage>(storage)) {
-      storage = storage();
-    }
-    if (typeof value === 'undefined') {
-      storage.removeItem(key);
-      setState(undefined);
-    } else if (isFunction<IFuncUpdater<T>>(value)) {
-      const previousState = getStoredValue();
-      const currentState = value(previousState);
-      storage.setItem(key, JSON.stringify(currentState));
-      setState(currentState);
-    } else {
-      storage.setItem(key, JSON.stringify(value));
-      setState(value);
-    }
-  }
-
-  return [state, updateState];
-}
-
-export default useStorageState;


Test patch:
diff --git a/packages/hooks/src/useStorageState/__tests__/index.test.ts b/packages/hooks/src/createUseStorageState/__tests__/index.test.ts
similarity index 90%
rename from packages/hooks/src/useStorageState/__tests__/index.test.ts
rename to packages/hooks/src/createUseStorageState/__tests__/index.test.ts
index 05a79f9f..b49d117c 100644
--- a/packages/hooks/src/useStorageState/__tests__/index.test.ts
+++ b/packages/hooks/src/createUseStorageState/__tests__/index.test.ts
@@ -1,5 +1,5 @@
 import { act, renderHook } from '@testing-library/react-hooks';
-import useStorageState, { IFuncUpdater } from '../index';
+import { IFuncUpdater, createUseStorageState } from '../index';
 
 class TestStorage implements Storage {
   [name: string]: any;
@@ -48,10 +48,11 @@ interface StorageStateProps<T> {
 describe('useStorageState', () => {
   const setUp = <T>(props: StorageStateProps<T>) => {
     const storage = new TestStorage();
+    const useStorageState = createUseStorageState(storage);
 
     return renderHook(
       ({ key, defaultValue }: StorageStateProps<T>) => {
-        const [state, setState] = useStorageState(storage, key, defaultValue);
+        const [state, setState] = useStorageState(key, defaultValue);
 
         return { state, setState };
       },
@@ -62,7 +63,7 @@ describe('useStorageState', () => {
   };
 
   it('should be defined', () => {
-    expect(useStorageState);
+    expect(createUseStorageState);
   });
 
   it('should get defaultValue for a given key', () => {