-
-
Save SimonHoiberg/7bebb7074247352e2ac178726adc5c64 to your computer and use it in GitHub Desktop.
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
import { useEffect, useRef, useState } from 'react'; | |
import { atom, RecoilState, useRecoilState } from 'recoil'; | |
export type DataPromise<T> = | |
| T | |
| Promise<T> | |
| ((prevState: T) => T) | |
| ((prevState: T) => Promise<T>); | |
/** | |
* Empty placeholder state for recoil | |
*/ | |
const placeholderState = atom({ key: 'placeholder', default: '' as any }); | |
/** | |
* Check if a component is still mounted. | |
* Use this when making asyncronous calls that results in updating state. | |
*/ | |
const useIsMounted = (): (() => boolean) => { | |
const isMounted = useRef(false); | |
useEffect(() => { | |
isMounted.current = true; | |
return () => { | |
isMounted.current = false; | |
}; | |
}, []); | |
return () => isMounted.current; | |
}; | |
/** | |
* Custom hook for handling async state updates | |
* @param recoilState | |
*/ | |
export const useAsyncState = <T>( | |
recoilState?: RecoilState<T>, | |
): [ | |
T | undefined, | |
(dataPromise: DataPromise<T>) => void, | |
{ isLoading: boolean; isFailed: boolean }, | |
] => { | |
const [data, setLocalData] = useState<T>(); | |
const [recoilData, setRecoilData] = useRecoilState(recoilState || placeholderState); | |
const [isLoading, setIsLoading] = useState(false); | |
const [isFailed, setIsFailed] = useState(false); | |
const isMounted = useIsMounted(); | |
const setData = async (dataPromise: DataPromise<T>) => { | |
try { | |
setIsLoading(true); | |
let result: T | undefined; | |
if (typeof dataPromise === 'function') { | |
const setter = recoilState ? setRecoilData : setLocalData; | |
const promiseFn = dataPromise as ((prevState: T) => T) | ((prevState: T) => Promise<T>); | |
result = await new Promise((resolve) => { | |
setter((prevData) => { | |
const data = prevData as T; | |
const result = promiseFn(data); | |
if (Promise.resolve(result) === result) { | |
result.then((processedResult) => resolve(processedResult)); | |
} else { | |
resolve(result); | |
} | |
return prevData; | |
}); | |
}); | |
} else { | |
result = await dataPromise; | |
} | |
if (!isMounted()) { | |
return; | |
} | |
if (recoilState) { | |
setRecoilData(result); | |
} else { | |
setLocalData(result); | |
} | |
setIsLoading(false); | |
} catch (error) { | |
console.error(error); | |
setIsFailed(true); | |
} | |
}; | |
return [ | |
recoilData || data, | |
setData, | |
{ | |
isLoading, | |
isFailed, | |
}, | |
]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment