79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
import * as React from 'react'
|
|
|
|
interface Opts<T> {
|
|
// The key used to store data in local storage.
|
|
key: string
|
|
|
|
// Converts the string value from local storage into a state object of type T.
|
|
parse: (value: string) => T
|
|
|
|
// Converts a state object of type T into a string that will be persisted to
|
|
// local storage.
|
|
stringify: (state: T) => string
|
|
}
|
|
|
|
// A React hook that adds component state whose value is persisted to local
|
|
// storage. Local storage is queried when the component is first rendered, and
|
|
// any update to the state value is written to local storage.
|
|
//
|
|
// Example:
|
|
//
|
|
// const [counter, setCounter] = usePersistedState({
|
|
// key: 'my-persisted-counter',
|
|
// parse: (value) => Number(value) || 0,
|
|
// stringify: (state) => String(state)
|
|
// })
|
|
//
|
|
// When reading data from local storage, be careful not to assume that the data
|
|
// is in any particular format. In particular, be wary of using type assertions
|
|
// (e.g. the `as` operator) in the `parse` callback.
|
|
//
|
|
// For persisted state that is serialized using JSON, see `usePersistedJSON`.
|
|
export function usePersistedState<T>(
|
|
opts: Opts<T>
|
|
): [T, React.Dispatch<React.SetStateAction<T>>] {
|
|
const [state, setState] = React.useState(() => {
|
|
return opts.parse(localStorage.getItem(opts.key) ?? '')
|
|
})
|
|
|
|
React.useEffect(() => {
|
|
localStorage.setItem(opts.key, opts.stringify(state))
|
|
}, [state])
|
|
|
|
return [state, setState]
|
|
}
|
|
|
|
// A React hook that adds component state whose value is persisted to local
|
|
// storage using JSON as the serialization format.
|
|
//
|
|
// const [point, setPoint] = usePersistedJSON('my-point', (data: any) => {
|
|
// try {
|
|
// return { x: data.x, y: data.y }
|
|
// } catch {
|
|
// return { x: 0, y: 0 }
|
|
// }
|
|
// })
|
|
//
|
|
// Be wary of using type assertions (e.g. the `as` operator) in the provided
|
|
// callback, as there is no guarantee that the value stored in local storage is
|
|
// in any particular format.
|
|
export function usePersistedJSON<T>(
|
|
key: string,
|
|
mapData: (data: any) => T
|
|
) {
|
|
return usePersistedState<T>({
|
|
key,
|
|
parse: (value) => {
|
|
let data: any = null
|
|
try { data = JSON.parse(value) } catch {}
|
|
return mapData(data)
|
|
},
|
|
stringify: JSON.stringify
|
|
})
|
|
}
|