-
Notifications
You must be signed in to change notification settings - Fork 145
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce
useStoredState()
hook (#4351)
- Loading branch information
Showing
6 changed files
with
123 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Dispatch, useCallback, useEffect, useState } from "react"; | ||
|
||
/** | ||
* A hook that allows you to get a specific item stored by the [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API). | ||
* Automatically updates the value when modified in the context of another document (such as an open tab) trough the [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event. | ||
* | ||
* @param storageArea The storage area to target, must implement the [`Storage`](https://developer.mozilla.org/en-US/docs/Web/API/Storage) interface (such as [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)). | ||
* @param keyName The key of the item to get from storage, same as passed to [`Storage.getItem()`](https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem) | ||
* @param The default value to fall back to in case no stored value was retrieved. | ||
*/ | ||
export function useStorageItem( | ||
storageArea: Storage, | ||
keyName: string, | ||
defaultValue: string | ||
): [string, Dispatch<string>] { | ||
const [value, setInnerValue] = useState( | ||
() => storageArea.getItem(keyName) ?? defaultValue | ||
); | ||
|
||
const setValue = useCallback((newValue: string) => { | ||
setInnerValue(newValue); | ||
|
||
// If the new value the same as the default value we can remove the item from storage. | ||
if (newValue === defaultValue) { | ||
storageArea.removeItem(keyName); | ||
} else { | ||
storageArea.setItem(keyName, newValue); | ||
} | ||
}, []); | ||
|
||
useEffect(() => { | ||
// If the key name, storage area or default value has changed, we want to update the value. | ||
// React will only set state if it actually changed, so no need to worry about re-renders. | ||
setInnerValue(storageArea.getItem(keyName) ?? defaultValue); | ||
|
||
// Subscribe to storage events so we can update the value when it is changed within the context of another document. | ||
window.addEventListener("storage", handleStorage); | ||
|
||
function handleStorage(event: StorageEvent) { | ||
// If the affected storage area is different we can ignore this event. | ||
// For example, if we're using session storage we're not interested in changes from local storage. | ||
if (event.storageArea !== storageArea) { | ||
return; | ||
} | ||
|
||
// If the event key is null then it means all storage was cleared. | ||
// Therefore we're interested in keys that are, or that match the key name. | ||
if (event.key === null || event.key === keyName) { | ||
setValue(event.newValue ?? defaultValue); | ||
} | ||
} | ||
|
||
return () => window.removeEventListener("storage", handleStorage); | ||
}, [storageArea, keyName, defaultValue]); | ||
|
||
return [value, setValue]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Dispatch, useCallback, useMemo } from "react"; | ||
import { useStorageItem } from "./useStorageItem"; | ||
|
||
/** | ||
* A hook that acts similarly to React's `useState()`, but persists the state using [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API). | ||
* Automatically updates the value when modified in the context of another document (such as an open tab) trough the [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event. | ||
* | ||
* The value is serialized as [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) and therefore the value provided must be serializable as such. | ||
* Because the value is always serialized it will never be referentially equal to originally provided value. | ||
* | ||
* @param storageArea The storage area to target, must implement the [`Storage`](https://developer.mozilla.org/en-US/docs/Web/API/Storage) interface (such as [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)). | ||
* @param keyName The key of the item to get from storage, same as passed to [`Storage.getItem()`](https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem) | ||
* @param defaultValue The default value to fall back to in case no stored value was retrieved (must be serializable as JSON). | ||
*/ | ||
export function useStoredState<S>( | ||
storageArea: Storage, | ||
keyName: string, | ||
defaultValue: S | ||
): [S, Dispatch<S>] { | ||
const defaultValueSerialized = useMemo( | ||
() => JSON.stringify(defaultValue), | ||
[defaultValue] | ||
); | ||
|
||
const [storedValue, setStoredValue] = useStorageItem( | ||
storageArea, | ||
keyName, | ||
defaultValueSerialized | ||
); | ||
|
||
const value = useMemo<S>(() => JSON.parse(storedValue), [storedValue]); | ||
const setValue = useCallback( | ||
(value: S) => setStoredValue(JSON.stringify(value)), | ||
[] | ||
); | ||
|
||
return [value, setValue]; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.