Browse Source

ref(useSessionStorage): sync set value and update hook deps (#37181)

* ref(useSessionStorage): sync set value and update hook deps

* ref(useSessionStorage): handle 'undefined'

* Update static/app/utils/useSessionStorage.tsx

Co-authored-by: Priscila Oliveira <priscila.oliveira@sentry.io>

* Update static/app/utils/useSessionStorage.tsx

Co-authored-by: Priscila Oliveira <priscila.oliveira@sentry.io>

* style(lint): Auto commit lint changes

Co-authored-by: Priscila Oliveira <priscila.oliveira@sentry.io>
Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
Jonas 2 years ago
parent
commit
3be1974af8
1 changed files with 48 additions and 33 deletions
  1. 48 33
      static/app/utils/useSessionStorage.tsx

+ 48 - 33
static/app/utils/useSessionStorage.tsx

@@ -1,50 +1,65 @@
-import {Dispatch, SetStateAction, useEffect, useState} from 'react';
+import {useCallback, useEffect, useState} from 'react';
+
+import sessionStorageWrapper from 'sentry/utils/sessionStorage';
 
 const isBrowser = typeof window !== 'undefined';
 
+function readStorageValue<T>(key, initialValue: T) {
+  const value = sessionStorage.getItem(key);
+
+  // We check for 'undefined' because the value may have
+  // previously been serialized as 'undefined'. This should no longer
+  // happen, but want to handle it gracefully.
+  if (value === null || value === 'undefined') {
+    return initialValue;
+  }
+
+  // Try parse storage value.
+  try {
+    return JSON.parse(value);
+  } catch {
+    // If parsing fails, return initial value.
+    return initialValue;
+  }
+}
+
 function useSessionStorage<T>(
   key: string,
   initialValue?: T
-): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] {
-  const [state, setState] = useState<T | undefined>(() => {
-    try {
-      // Get from session storage by key
-      const sessionStorageValue = sessionStorage.getItem(key);
-
-      if (sessionStorageValue === 'undefined') {
-        return initialValue;
-      }
-
-      // Parse stored json or if none return initialValue
-      return sessionStorageValue ? JSON.parse(sessionStorageValue) : initialValue;
-    } catch {
-      // If user is in private mode or has storage restriction
-      // sessionStorage can throw. JSON.parse and JSON.stringify
-      // can throw, too.
-      return initialValue;
-    }
-  });
+): [T | undefined, (value: T | undefined) => void, () => void] {
+  const [state, setState] = useState<T | undefined>(() =>
+    readStorageValue(key, initialValue)
+  );
 
   useEffect(() => {
-    try {
-      const serializedState = JSON.stringify(state);
-      sessionStorage.setItem(key, serializedState);
-    } catch {
-      // If user is in private mode or has storage restriction
-      // sessionStorage can throw. Also JSON.stringify can throw.
-    }
-  }, [state]);
-
-  function removeItem() {
-    sessionStorage.removeItem(key);
+    setState(readStorageValue(key, initialValue));
+    // We want to re-initialized the storage value only when the key changes.
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [key]);
+
+  const wrappedSetState = useCallback(
+    (value: T | undefined) => {
+      setState(value);
+
+      try {
+        sessionStorageWrapper.setItem(key, JSON.stringify(value));
+      } catch {
+        // Best effort and just update the in-memory value.
+      }
+    },
+    [key]
+  );
+
+  const removeItem = useCallback(() => {
     setState(undefined);
-  }
+    sessionStorageWrapper.removeItem(key);
+  }, [key]);
 
   if (!isBrowser) {
     return [initialValue, () => {}, () => {}];
   }
 
-  return [state, setState, removeItem];
+  return [state, wrappedSetState, removeItem];
 }
 
 export default useSessionStorage;