feat: store notification filter state in localStorage

This commit is contained in:
Jordan Knott 2021-11-04 11:27:26 -05:00
parent de6fe78004
commit f9a5007104
3 changed files with 93 additions and 2 deletions

View File

@ -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,

View File

@ -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 };

View File

@ -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',
}; };