Skip to content

Instantly share code, notes, and snippets.

@SimonHoiberg
Created January 24, 2021 10:39
Show Gist options
  • Save SimonHoiberg/7bebb7074247352e2ac178726adc5c64 to your computer and use it in GitHub Desktop.
Save SimonHoiberg/7bebb7074247352e2ac178726adc5c64 to your computer and use it in GitHub Desktop.
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