Data fetching in React

Image from Federico Beccari on Unsplash

Motivations

Types of Data fetching

Data fetching basics

What is a side effect ?

Given fixed inputs, you can’t predict your function’s output at each call: that’s an impure function, or a function with a side effect.

React.useEffect(
function () {
fetchData(filters, pagination);
},
[filters, pagination] // when filters or pagination change, reload data
);

The fetching state

const FetchStatus = Object.freeze({
INITIAL: "initial",
LOADING: "loading",
SUCCESS: "success",
ERROR: "error",
});
const initalState = Object.freeze({
status: FetchStatus.INITIAL,
content: null,
});
const loadingState = {
status: FetchStatus.LOADING,
content: { requestData: { page: 0, size: 10 } }
};
const successState = {
status: FetchStatus.SUCCESS,
content: { data: {/*...*/}, requestData: { username: "Sophie" } }
};
const errorState = {
status: FetchStatus.ERROR,
content: { error: {/*...*/}, requestData: { bankId: 12 } }
};
function useFetchOperation({
// will be used to controle when to start the fetch
// for example: condition=isHovered, condition=!!debouncedUsername
condition,
// defines whether this fetch action can be reloadable
reloadable,
// a callback fired (if present) when the fetch state changes
onStateChange,

/* depending on your project, this is the part that you may tune:
- if working with redux, you can pass the actionCreator and its arguments
- if working with axios directly, the promise and its arguments go here
*/
// the url
url,
// the request function options
options,

retryable,
retryDelay,
maxRetries,

indicator = true,
// the component to be mounted if indicator is true
// ts receives the fetch state and the reload function
// can be used to tune if to display an inline loader or a dialog one
IndicatorComponent,
// contextual props to the indicator component
IndicatorComponentProps,
}) {
/**
* ... magic goes here
* Not exactly magic, but to shorten this post, I'll omit this part
* but it will be accessible in the codesandbox examples ;-)
*/
React.useEffect(() => {
reload();
}, [memoizedDependencies.current]);

return {
state,
reload,
error: state.status === FetchStatus.ERROR,
loading: state.status === FetchStatus.LOADING,
success: state.status === FetchStatus.SUCCESS,
indicator: indicator && <IndicatorComponent {...IndicatorProps} />,
};
}
return (
<>
<FetchTrigger url="/pokemons" options={ type: ["fire", "water"] } onSuccess={fn} onError={fn} />
<ComponentAccessingDataSomehow />
</>
);
const { perform } = useManualFetchOperation(/* ... */)
// later
<button onClick={perform}>Click me</button>
// will not have an indicator (ie silent requests)
function useGhostFetchOperation({ ...args }) {
return useFetchOperation({ ...args, indicator: false });
}
// will display a dialog with spinner when loading
// and thus preventing the user from clicking in the page
function useGlobalFetchOperation({ ...args }) {
return useFetchOperation({ ...args, IndicatorComponent: DialogIndicator });
}
// ... and so on

Can’t perform a React state update on an unmounted component

// deep inside React
console.error(
"Can't perform a React state update on an unmounted component. This " +
'is a no-op, but it indicates a memory leak in your application. To ' +
'fix, cancel all subscriptions and asynchronous tasks in %s.',
tag === ClassComponent
? 'the componentWillUnmount method'
: 'a useEffect cleanup function',
);
// this is a simple a naive way to handle the problem!
React.useEffect(
function() {
// this code is not production ready!!
let didCancel = false;
fetchData(
requestData,
function onSuccess(data) {
if (!didCancel) setState(data);
},
function onError(error){
if (!didCancel) setError(error);
}
);
return () => { didCancel = true; }
},
[dependencies]
);

User input optimizations (debounce and throttle)

Conclusion

Note

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store