useLocalStorage(key, defaultValue, {serialize,
deserialize})
use it just like setState with integrated saving to
localStorage:
import {useLocalStorageState} from './utils'
// ...
const [squares, setSquares] = useLocalStorageState(
'squares',
Array(9).fill(null),
)
// ...
setSquares(Array(9).fill(null))
from react-hooks workshop - exercise #2 (react-hooks/src/utils.js)
import * as React from 'react'
/**
*
* @param {String} key The key to set in localStorage for this value
* @param {Object} defaultValue The value to use if it is not already in localStorage
* @param {{serialize: Function, deserialize: Function}} options The serialize and deserialize functions to use
* (defaults to JSON.stringify and JSON.parse respectively)
*/
function useLocalStorageState(
key,
defaultValue = '',
{serialize = JSON.stringify, deserialize = JSON.parse} = {},
) {
const [state, setState] = React.useState(() => {
const valueInLocalStorage = window.localStorage.getItem(key)
if (valueInLocalStorage) {
return deserialize(valueInLocalStorage)
}
return typeof defaultValue === 'function' ? defaultValue() : defaultValue
})
const prevKeyRef = React.useRef(key)
React.useEffect(() => {
const prevKey = prevKeyRef.current
if (prevKey !== key) {
window.localStorage.removeItem(prevKey)
}
prevKeyRef.current = key
window.localStorage.setItem(key, serialize(state))
}, [key, state, serialize])
return [state, setState]
}
export {useLocalStorageState}
useAsync(initialState)
Hook for managing the state of a data-fetching-promise. Managing async state is something every app does all the time so this abstracts that away into a custom hook.
Simple example usage:
import {useAsync} from './utils'
const {
// using the same names that react-query uses for convenience
isIdle,
isLoading,
isError,
isSuccess,
setData,
setError,
error,
status,
data,
run,
reset,
} = useAsync()
React.useEffect(() => {
run(fetchPokemon(pokemonName))
}, [pokemonName, run])
Example for fetching pokemon-data with it in a
PokemonInfo component:
import {useAsync} from './utils'
function PokemonInfo({pokemonName}) {
const [cache, dispatch] = usePokemonCache()
const {data: pokemon, status, error, run, setData} = useAsync({
status: pokemonName ? 'pending' : 'idle',
})
React.useEffect(() => {
if (!pokemonName) {
return
} else if (cache[pokemonName]) {
setData(cache[pokemonName])
} else {
run(
fetchPokemon(pokemonName).then(pokemonData => {
dispatch({type: 'ADD_POKEMON', pokemonName, pokemonData})
return pokemonData
}),
)
}
}, [cache, dispatch, pokemonName, run, setData])
if (status === 'idle') {
return 'Submit a pokemon'
} else if (status === 'pending') {
return <PokemonInfoFallback name={pokemonName} />
} else if (status === 'rejected') {
throw error
} else if (status === 'resolved') {
return <PokemonDataView pokemon={pokemon} />
}
throw new Error('This should be impossible')
}
from
advanced-react-hooks workshop - exercise #2
and
this issue to only dispatch the most recent fetch
(react-performance/src/utils.js, see also
03.extra-2.js)
import * as React from 'react'
function useSafeDispatch(dispatch) {
const mounted = React.useRef(false)
React.useLayoutEffect(() => {
mounted.current = true
return () => (mounted.current = false)
}, [])
return React.useCallback(
(...args) => (mounted.current ? dispatch(...args) : void 0),
[dispatch],
)
}
const defaultInitialState = {status: 'idle', data: null, error: null}
function useAsync(initialState) {
const initialStateRef = React.useRef({
...defaultInitialState,
...initialState,
})
const [{status, data, error}, setState] = React.useReducer(
(s, a) => ({...s, ...a}),
initialStateRef.current,
)
const safeSetState = useSafeDispatch(setState)
const mostRecentPromise = React.useRef(null)
const run = React.useCallback(
promise => {
if (!promise || !promise.then) {
throw new Error(
`The argument passed to useAsync().run must be a promise.
Maybe a function that's passed isn't returning anything?`,
)
}
safeSetState({ status: 'pending' })
mostRecentPromise.current = promise
return promise.then(
data => {
if (promise === mostRecentPromise.current) {
safeSetState({ data, status: 'resolved' })
}
return data
},
error => {
if (promise === mostRecentPromise.current) {
safeSetState({ status: 'rejected', error })
}
return error
},
)
},
[safeSetState],
)
const setData = React.useCallback(data => safeSetState({data}), [
safeSetState,
])
const setError = React.useCallback(error => safeSetState({error}), [
safeSetState,
])
const reset = React.useCallback(() => safeSetState(initialStateRef.current), [
safeSetState,
])
return {
// using the same names that react-query uses for convenience
isIdle: status === 'idle',
isLoading: status === 'pending',
isError: status === 'rejected',
isSuccess: status === 'resolved',
setData,
setError,
error,
status,
data,
run,
reset,
}
}
createResource(promise)
A generic "resource factory" to be used in conjunction with
<React.Suspense />.
Useful for Render as you fetch.
See also the comment on the right.
Example usage:
import {createResource} from './utils'
function PokemonInfo({pokemonResource}) {
const pokemon = pokemonResource.read()
return (
<div>
<div className="pokemon-info__img-wrapper">
<img src={pokemon.image} alt={pokemon.name} />
</div>
<PokemonDataView pokemon={pokemon} />
</div>
)
}
// ...
fucntion App() {
const [pokemonResource, setPokemonResource] = React.useState(null)
// ...
setPokemonResource(
createResource(fetch(pokemonName).then((response) => response.json()))
);
// ...
return (
<React.Suspense
fallback={<PokemonInfoFallback name={pokemonName} />}
>
<PokemonInfo pokemonResource={pokemonResource} />
</React.Suspense>
)
}
from react-suspense workshop - exercise #1.extra-2 (react-suspense/src/utils.js)
// 🚨 This should NOT be copy/pasted for production code and is only here
// for experimentation purposes. The API for suspense (currently throwing a
// promise) is likely to change before suspense is officially released.
// This was strongly inspired by work done in the React Docs by Dan Abramov
function createResource(promise) {
let status = 'pending'
let result = promise.then(
resolved => {
status = 'success'
result = resolved
},
rejected => {
status = 'error'
result = rejected
},
)
return {
read() {
if (status === 'pending') throw result
if (status === 'error') throw result
if (status === 'success') return result
throw new Error('This should be impossible')
},
}
}
export {createResource}
preloadImage(src)
Example usage to create a <Img /> component in
conjunction with createResource(promise) from above:
function Img({src, alt, ...props}) {
const imgSrcResource = createResource(preloadImage(src))
return <img src={imgSrcResource.read()} alt={alt} {...props} />
}
// and the Example code from createResource(promise),
// but with the native <img src={pokemon.image} alt={pokemon.name} //> replaced by this component
from react-suspense workshop - exercise #5 (react-suspense/src/utils.js)
function preloadImage(src) {
return new Promise(resolve => {
const img = document.createElement('img')
img.src = src
img.onload = () => resolve(src)
})
}
export {preloadImage}