Skip to content

Instantly share code, notes, and snippets.

@Munawwar
Last active July 22, 2021 15:23
Show Gist options
  • Save Munawwar/4fd9502f14c60828a99de6500fd8b3e5 to your computer and use it in GitHub Desktop.
Save Munawwar/4fd9502f14c60828a99de6500fd8b3e5 to your computer and use it in GitHub Desktop.
Async state fetching hook (for react)
import { useEffect, useState } from 'react';
/**
* @template T
* @param {(previousState: T) => Promise<T>} getAsyncState async function to fetch state
* @param {T} initialState
* @param {any[]} [dependencies=[]] similar to useEffect dependencies. change in dependencies will
* automatically call getAsyncState again
* @returns {[T, (newState: T) => void, () => Promise<void>]} [state, setState, refetch]
*/
function useAsyncState(
getAsyncState,
initialState,
dependencies = [],
) {
const [state, setState] = useState(initialState);
let ignoreFetch = false;
const refetch = async () => {
const newState = await getAsyncState(state);
if (!ignoreFetch) {
setState(newState);
}
};
useEffect(() => {
refetch();
return () => {
// ignore fetch because either a new request has begun, making the in-flight request
// potentially stale or component got destroyed, meaning you shouldn't update anything now.
ignoreFetch = true;
};
}, dependencies);
return [state, setState, refetch];
}
export default useAsyncState;
@Munawwar
Copy link
Author

Munawwar commented Jul 8, 2021

Example 1

const [pageData] = useAsyncState(() => axios.get('/page/stuff'), {});

Example 2 (builds on example 1):

 // outside component
const areasCache = {};
const fetchAreas = async () => Promise.resolve([`Area ${Math.trunc(Math.random()*10)}`]);

// inside component after Example 1's useAsyncState
// ...
const { selectedCity } = pageData;
const [areas] = useAsyncState(
  async (currentValue) => {
    if (!selectedCity) {
        return currentValue;
    }
    areasCache[selectedCity] = areasCache[selectedCity] || await fetchAreas(selectedCity);
    return areasCache[selectedCity];
  },
  areasCache[selectedCity] || [],
  [selectedCity],
);

@Munawwar
Copy link
Author

Example 3:

const [settings, setSettings] = getAsyncState(async () => axios.get('/settings'));

// form fields can use setSettings() now, like regular setState
setSettings({ name: event.target.value });

Example 4, fetch a list based on search text:

// outside component, debounced search function
const search = debounce((text) => axios.get('/search', { params: { q: text } }), 200);

// inside component
const [searchText, setSearchText] = useState('');
const [{ loading, results }, setList] = getAsyncState(
  async (currentState) => {
    setList({ loading: true, results: currentState.results });
    const results = await search(searchText);
    return { loading: false, results };
  },
  { loading: true, results: [] },
  [searchText],
);

// inside search onChange handler all you go to do is setSearchText(newText);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment