feat: store notification filter state in localStorage
This commit is contained in:
		@@ -15,6 +15,8 @@ import dayjs from 'dayjs';
 | 
				
			|||||||
import { Popup, usePopup } from 'shared/components/PopupMenu';
 | 
					import { Popup, usePopup } from 'shared/components/PopupMenu';
 | 
				
			||||||
import { CheckCircleOutline, Circle, CircleSolid, UserCircle } from 'shared/icons';
 | 
					import { CheckCircleOutline, Circle, CircleSolid, UserCircle } from 'shared/icons';
 | 
				
			||||||
import produce from 'immer';
 | 
					import produce from 'immer';
 | 
				
			||||||
 | 
					import { useLocalStorage } from 'shared/hooks/useStateWithLocalStorage';
 | 
				
			||||||
 | 
					import localStorage from 'shared/utils/localStorage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ItemWrapper = styled.div`
 | 
					const ItemWrapper = styled.div`
 | 
				
			||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
@@ -403,7 +405,10 @@ type NotificationEntry = {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const NotificationPopup: React.FC = ({ children }) => {
 | 
					const NotificationPopup: React.FC = ({ children }) => {
 | 
				
			||||||
  const [filter, setFilter] = useState<NotificationFilter>(NotificationFilter.Unread);
 | 
					  const [filter, setFilter] = useLocalStorage<NotificationFilter>(
 | 
				
			||||||
 | 
					    localStorage.NOTIFICATIONS_FILTER,
 | 
				
			||||||
 | 
					    NotificationFilter.Unread,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  const [data, setData] = useState<{ nodes: Array<NotificationEntry>; hasNextPage: boolean; cursor: string }>({
 | 
					  const [data, setData] = useState<{ nodes: Array<NotificationEntry>; hasNextPage: boolean; cursor: string }>({
 | 
				
			||||||
    nodes: [],
 | 
					    nodes: [],
 | 
				
			||||||
    hasNextPage: false,
 | 
					    hasNextPage: false,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,14 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A wrapper for "JSON.parse()"" to support "undefined" value
 | 
				
			||||||
 | 
					function parseJSON<T>(value: string | null): T | undefined {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return value === 'undefined' ? undefined : JSON.parse(value ?? '');
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.log('parsing error on', { value });
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
 | 
					const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
 | 
				
			||||||
  const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
 | 
					  const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
 | 
				
			||||||
@@ -11,3 +21,78 @@ const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispa
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default useStateWithLocalStorage;
 | 
					export default useStateWithLocalStorage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SetValue<T> = Dispatch<SetStateAction<T>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>] {
 | 
				
			||||||
 | 
					  // Get from local storage then
 | 
				
			||||||
 | 
					  // parse stored json or return initialValue
 | 
				
			||||||
 | 
					  const readValue = (): T => {
 | 
				
			||||||
 | 
					    // Prevent build error "window is undefined" but keep keep working
 | 
				
			||||||
 | 
					    if (typeof window === 'undefined') {
 | 
				
			||||||
 | 
					      return initialValue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const item = window.localStorage.getItem(key);
 | 
				
			||||||
 | 
					      return item ? (parseJSON(item) as T) : initialValue;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.warn(`Error reading localStorage key “${key}”:`, error);
 | 
				
			||||||
 | 
					      return initialValue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // State to store our value
 | 
				
			||||||
 | 
					  // Pass initial state function to useState so logic is only executed once
 | 
				
			||||||
 | 
					  const [storedValue, setStoredValue] = useState<T>(readValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Return a wrapped version of useState's setter function that ...
 | 
				
			||||||
 | 
					  // ... persists the new value to localStorage.
 | 
				
			||||||
 | 
					  const setValue: SetValue<T> = (value) => {
 | 
				
			||||||
 | 
					    // Prevent build error "window is undefined" but keeps working
 | 
				
			||||||
 | 
					    if (typeof window === 'undefined') {
 | 
				
			||||||
 | 
					      console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      // Allow value to be a function so we have the same API as useState
 | 
				
			||||||
 | 
					      const newValue = value instanceof Function ? value(storedValue) : value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Save to local storage
 | 
				
			||||||
 | 
					      window.localStorage.setItem(key, JSON.stringify(newValue));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Save state
 | 
				
			||||||
 | 
					      setStoredValue(newValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // We dispatch a custom event so every useLocalStorage hook are notified
 | 
				
			||||||
 | 
					      window.dispatchEvent(new Event('local-storage'));
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.warn(`Error setting localStorage key “${key}”:`, error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setStoredValue(readValue());
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const handleStorageChange = () => {
 | 
				
			||||||
 | 
					      setStoredValue(readValue());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // this only works for other documents, not the current one
 | 
				
			||||||
 | 
					    window.addEventListener('storage', handleStorageChange);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // this is a custom event, triggered in writeValueToLocalStorage
 | 
				
			||||||
 | 
					    window.addEventListener('local-storage', handleStorageChange);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      window.removeEventListener('storage', handleStorageChange);
 | 
				
			||||||
 | 
					      window.removeEventListener('local-storage', handleStorageChange);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [storedValue, setValue];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { useLocalStorage };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const localStorage = {
 | 
					const localStorage = {
 | 
				
			||||||
 | 
					  NOTIFICATIONS_FILTER: 'notifications_filter',
 | 
				
			||||||
  CARD_LABEL_VARIANT_STORAGE_KEY: 'card_label_variant',
 | 
					  CARD_LABEL_VARIANT_STORAGE_KEY: 'card_label_variant',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user