Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
useLocationState: a glorified location.state for React Router as a hook
import { useState, useEffect } from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import { useSessionStorage } from './use-storage'; // see https://gist.github.com/donaldpipowitch/7310d7b9e4b6d467134c425e8732adc6
type UseLocationStateOptions<T> = {
defaultValue?: T;
scope?: string;
filter?: (value: T) => boolean;
};
// this is a glorified `location.state` (needed, because the vanilla
// location state has stricter size limits than `sessionStorage`)
// you can use this as a replacement to `sessionStorage`, if you
// want save "refresh"/"navigate back" behaviour, but if you aren't
// interested in "navigate away"
// (by default the state is _global_, but you can use a scope prefix
// to create individual state)
export function useLocationState<T>(options: UseLocationStateOptions<T> = {}) {
const { defaultValue, scope, filter } = options;
const sessionStorage = useSessionStorage();
const { key } = useLocation();
const { replace } = useHistory();
const scopedKey = key && scope ? `${key}.${scope}` : key;
const [state, setState] = useState(() => {
if (!scopedKey) return defaultValue;
const savedState = sessionStorage.getItem(scopedKey);
if (!savedState) return defaultValue;
return JSON.parse(savedState) as T;
});
// every time the state changes and we don't filter it a new location.key
// will be generated
useEffect(() => {
if (filter && !filter(state as T)) return;
replace({});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state]);
// every time a new location.key was generated we store the state locally
useEffect(() => {
if (!scopedKey) return;
sessionStorage.setItem(scopedKey, JSON.stringify(state));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [scopedKey]);
return [state, setState] as const;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment