Asynchronous initial state
nandorojo opened this issue · 4 comments
Hey, thanks for the awesome package.
I have a question. I'm looking to implement something similar to Redux Persist. Is there a recommended approach for something like this?
const initialState = {
query: async () => {
const cachedQuery = await AsyncStorate.get('query')
return cachedQuery || ''
}
}
const { useGlobalState } = createGlobalState(initialState)
Maybe this makes more sense:
const initialState = {
query: ''
}
const { useGlobalState } = createGlobalState(initialState)
const useGlobalQuery() {
const [query, setQuery] = useGlobalState('query')
const [loading, setLoading] = useState(true)
useEffect(() => {
(async () => {
const cachedQuery = await AsyncStorate.get('query')
if (cachedQuery) {
setQuery(cachedQuery)
}
setLoading(false)
})()
}, [])
useEffect(() => {
AsyncStorage.set('query', query)
}, [query])
return [query, setQuery, { loading }] as const
}
Figured I would see what you suggest.
Hi, thanks for coming.
The first one won't work. The second one looks good.
Another option is to do it outside React. For example:
const initialState = {
query: ''
}
const { useGlobalState, setGlobalState } = createGlobalState(initialState)
(async () => {
const cachedQuery = await AsyncStorate.get('query')
if (cachedQuery) {
setGlobalState('query', cachedQuery)
}
})()
Hope it helps!
Ah I didn't realize that setGlobalState
was globally accessible, I was looking for that. Thanks!
In case anyone sees this, I came up with a wrapper, useInitialStateFromAsyncStorage
, which I use anywhere to store / get state from local storage.
const initialState = {
query: ''
}
const { useGlobalState, setGlobalState } = createGlobalState(initialState)
const useQueryState = () => {
const [query, setQuery] = useGlobalState('query')
return useInitialStateFromAsyncStorage([query, setQuery], { asyncStorageKey: 'query' })
}
I then use it like this:
const [query, setQuery] = useQueryState()
The code for useInitialStateFromAsyncStorage
is as follows:
import type { SetStateAction } from 'react'
import { useRef, useEffect } from 'react'
import AsyncStorage from '@react-native-community/async-storage'
import { useDebounce } from '@beatgig/hooks/use-debouce'
type Props<State> = [state: State, setState: (u: SetStateAction<State>) => void]
type Options = {
asyncStorageKey: string
debounce?: number
}
export default function useInitialStateFromAsyncStorage<State>(
[state, setState]: Props<State>,
{ asyncStorageKey, debounce = 200 }: Options
) {
const hasStateBeenUpdated = useRef(false)
const mounted = useRef(false)
useEffect(() => {
if (mounted.current) {
hasStateBeenUpdated.current = true
} else {
mounted.current = true
}
}, [state])
useEffect(() => {
const getFromAsyncStorage = async () => {
const fromStorage: null | string = await AsyncStorage.getItem(
asyncStorageKey
)
console.log('[use-initial-state-from-async-storage] get item', {
asyncStorageKey,
fromStorage,
})
// only set it from local storage if we haven't changed the state yet, meaning it's equal to its initial state
if (fromStorage && !hasStateBeenUpdated.current) {
let newState: { value: State } | null = null
try {
newState = JSON.parse(fromStorage)
if (newState?.value) {
hasStateBeenUpdated.current = true // not necessary but whatever
setState(newState?.value)
}
} catch (e) {
console.error(
`[use-initial-state-from-async-storage] error getting from local storage ${e}`,
{ newState }
)
}
}
}
getFromAsyncStorage()
// these will never change
}, [asyncStorageKey, setState])
const debouncedState = useDebounce(state, debounce)
const debouncedStateString = JSON.stringify({ value: debouncedState })
useEffect(() => {
if (debouncedStateString) {
console.log('[use-initial-state-from-async-storage] set item', {
asyncStorageKey,
debouncedStateString,
})
AsyncStorage.setItem(asyncStorageKey, debouncedStateString)
}
}, [asyncStorageKey, debouncedStateString])
return [state, setState] as const
}
I assume you could achieve the same with localStorage
in place of AsyncStorage
.
Actually, we have wiki https://github.com/dai-shi/react-hooks-global-state/wiki
Feel free to add new pages.