diff --git a/app/javascript/flavours/glitch/features/account_timeline/v2/context.tsx b/app/javascript/flavours/glitch/features/account_timeline/v2/context.tsx index 118003ebf6..04586b2206 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/v2/context.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/v2/context.tsx @@ -7,7 +7,7 @@ import { useState, } from 'react'; -import { useStorage } from '@/flavours/glitch/hooks/useStorage'; +import { useStorageState } from '@/flavours/glitch/hooks/useStorage'; interface AccountTimelineContextValue { accountId: string; @@ -26,30 +26,34 @@ export const AccountTimelineProvider: FC<{ accountId: string; children: ReactNode; }> = ({ accountId, children }) => { - const { getItem, setItem } = useStorage({ + const storageOptions = { type: 'session', prefix: `filters-${accountId}:`, - }); - const [boosts, setBoosts] = useState( - () => (getItem('boosts') === '0' ? false : true), // Default to enabled. + } as const; + + const [boosts, setBoosts] = useStorageState( + 'boosts', + true, + storageOptions, ); - const [replies, setReplies] = useState(() => - getItem('replies') === '1' ? true : false, + + const [replies, setReplies] = useStorageState( + 'replies', + false, + storageOptions, ); const handleSetBoosts = useCallback( (value: boolean) => { setBoosts(value); - setItem('boosts', value ? '1' : '0'); }, - [setBoosts, setItem], + [setBoosts], ); const handleSetReplies = useCallback( (value: boolean) => { setReplies(value); - setItem('replies', value ? '1' : '0'); }, - [setReplies, setItem], + [setReplies], ); const [showAllPinned, setShowAllPinned] = useState(false); diff --git a/app/javascript/flavours/glitch/hooks/useStorage.ts b/app/javascript/flavours/glitch/hooks/useStorage.ts index 6ee64217d2..d4af1df6cc 100644 --- a/app/javascript/flavours/glitch/hooks/useStorage.ts +++ b/app/javascript/flavours/glitch/hooks/useStorage.ts @@ -1,9 +1,14 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; + +interface StorageOptions { + type?: 'local' | 'session'; + prefix?: string; +} export function useStorage({ type = 'local', prefix = '', -}: { type?: 'local' | 'session'; prefix?: string } = {}) { +}: StorageOptions = {}) { const storageType = type === 'local' ? 'localStorage' : 'sessionStorage'; const isAvailable = useMemo( () => storageAvailable(storageType), @@ -23,6 +28,7 @@ export function useStorage({ }, [isAvailable, storageType, prefix], ); + const setItem = useCallback( (key: string, value: string) => { if (!isAvailable) { @@ -35,13 +41,52 @@ export function useStorage({ [isAvailable, storageType, prefix], ); + const removeItem = useCallback( + (key: string) => { + if (!isAvailable) { + return; + } + try { + window[storageType].removeItem(prefix ? `${prefix};${key}` : key); + } catch {} + }, + [isAvailable, storageType, prefix], + ); + return { isAvailable, getItem, setItem, + removeItem, }; } +export function useStorageState( + key: string, + initialState: T, + options?: StorageOptions, +) { + const { getItem, setItem, removeItem } = useStorage(options); + const [state, setState] = useState( + () => (retrieveBooleanOrString(getItem(key)) as T | null) ?? initialState, + ); + + const handleSetState = useCallback( + (newValue: T) => { + setItem(key, castToString(newValue)); + setState(newValue); + }, + [key, setItem], + ); + + const removeState = useCallback(() => { + removeItem(key); + setState(initialState); + }, [initialState, key, removeItem]); + + return [state, handleSetState, removeState] as const; +} + // Tests the storage availability for the given type. Taken from MDN: // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API export function storageAvailable(type: 'localStorage' | 'sessionStorage') { @@ -62,3 +107,21 @@ export function storageAvailable(type: 'localStorage' | 'sessionStorage') { ); } } + +function castToString(value: string | boolean) { + if (typeof value === 'boolean') { + return value ? '1' : '0'; + } else { + return value; + } +} + +function retrieveBooleanOrString(value: string | null) { + if (value === '1') { + return true; + } else if (value === '0') { + return false; + } else { + return value; + } +}