Add useStorageState hook (#37825)

This commit is contained in:
diondiondion 2026-02-11 14:30:53 +01:00 committed by GitHub
parent bbd88d356d
commit f99c60a8f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 13 deletions

View File

@ -7,7 +7,7 @@ import {
useState, useState,
} from 'react'; } from 'react';
import { useStorage } from '@/mastodon/hooks/useStorage'; import { useStorageState } from '@/mastodon/hooks/useStorage';
interface AccountTimelineContextValue { interface AccountTimelineContextValue {
accountId: string; accountId: string;
@ -26,30 +26,34 @@ export const AccountTimelineProvider: FC<{
accountId: string; accountId: string;
children: ReactNode; children: ReactNode;
}> = ({ accountId, children }) => { }> = ({ accountId, children }) => {
const { getItem, setItem } = useStorage({ const storageOptions = {
type: 'session', type: 'session',
prefix: `filters-${accountId}:`, prefix: `filters-${accountId}:`,
}); } as const;
const [boosts, setBoosts] = useState(
() => (getItem('boosts') === '0' ? false : true), // Default to enabled. const [boosts, setBoosts] = useStorageState<boolean>(
'boosts',
true,
storageOptions,
); );
const [replies, setReplies] = useState(() =>
getItem('replies') === '1' ? true : false, const [replies, setReplies] = useStorageState<boolean>(
'replies',
false,
storageOptions,
); );
const handleSetBoosts = useCallback( const handleSetBoosts = useCallback(
(value: boolean) => { (value: boolean) => {
setBoosts(value); setBoosts(value);
setItem('boosts', value ? '1' : '0');
}, },
[setBoosts, setItem], [setBoosts],
); );
const handleSetReplies = useCallback( const handleSetReplies = useCallback(
(value: boolean) => { (value: boolean) => {
setReplies(value); setReplies(value);
setItem('replies', value ? '1' : '0');
}, },
[setReplies, setItem], [setReplies],
); );
const [showAllPinned, setShowAllPinned] = useState(false); const [showAllPinned, setShowAllPinned] = useState(false);

View File

@ -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({ export function useStorage({
type = 'local', type = 'local',
prefix = '', prefix = '',
}: { type?: 'local' | 'session'; prefix?: string } = {}) { }: StorageOptions = {}) {
const storageType = type === 'local' ? 'localStorage' : 'sessionStorage'; const storageType = type === 'local' ? 'localStorage' : 'sessionStorage';
const isAvailable = useMemo( const isAvailable = useMemo(
() => storageAvailable(storageType), () => storageAvailable(storageType),
@ -23,6 +28,7 @@ export function useStorage({
}, },
[isAvailable, storageType, prefix], [isAvailable, storageType, prefix],
); );
const setItem = useCallback( const setItem = useCallback(
(key: string, value: string) => { (key: string, value: string) => {
if (!isAvailable) { if (!isAvailable) {
@ -35,13 +41,52 @@ export function useStorage({
[isAvailable, storageType, prefix], [isAvailable, storageType, prefix],
); );
const removeItem = useCallback(
(key: string) => {
if (!isAvailable) {
return;
}
try {
window[storageType].removeItem(prefix ? `${prefix};${key}` : key);
} catch {}
},
[isAvailable, storageType, prefix],
);
return { return {
isAvailable, isAvailable,
getItem, getItem,
setItem, setItem,
removeItem,
}; };
} }
export function useStorageState<T extends string | boolean>(
key: string,
initialState: T,
options?: StorageOptions,
) {
const { getItem, setItem, removeItem } = useStorage(options);
const [state, setState] = useState<T>(
() => (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: // 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 // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
export function storageAvailable(type: 'localStorage' | 'sessionStorage') { 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;
}
}