visit
Consider the following example:
import React, { useState, useEffect } from "react";
const Todos = () => {
const [todos, setTodos] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); useEffect(() => {
const init = async () => {
try {
setLoading(true);
const response = await fetch(
"//jsonplaceholder.typicode.com/todos"
);
const data = await response.json();
setTodos(data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
init();
}, []); if (loading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
};
const Todo = ({ id }) => {
const [todo, setTodo] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); useEffect(() => {
const init = async () => {
try {
setLoading(true);
const response = await fetch(
`//jsonplaceholder.typicode.com/todos/${id}`
);
const data = await response.json();
setTodo(data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
init();
}, [id]); if (loading) return <div>Loading 2...</div>;
if (error) return <div>Error 2</div>;
return (
<div>
<h1>{todo.title}</h1>
</div>
);
};
import React, { useState, useEffect } from "react";const useAsync = (defaultData) => {
const [data, setData] = useState({
data: defaultData ?? null,
error: null,
loading: false,
}); const run = async (asyncFn) => {
try {
setData({ data: null, error: null, loading: true });
const response = await asyncFn();
const result = { data: response, error: null, loading: false };
setData(result);
return result;
} catch (error) {
const result = { data: null, error, loading: false };
setData(result);
return result;
}
}; return {
...data,
run,
};
};
import React, { useState, useEffect } from "react";
import { useAsync } from "./hooks";
const Todos = () => {
const { data, loading, error, run } = useAsync([]); useEffect(() => {
run(() => fetch("//jsonplaceholder.typicode.com/todos").then((res) => res.json()));
}, []); // Same as above
return ...
};
import React, { useState, useEffect } from "react";
import { useAsync } from "./hooks";
const Todo = ({ id }) => {
const { data, loading, error, run } = useAsync(null); useEffect(() => {
run(() => fetch(`//jsonplaceholder.typicode.com/todos/${id}`).then((res) => res.json()));
}, [id]); // Same as above
return ...
};
NOTE: We have reduced the amount of code we have to write by using the custom hook. It’s also easier to read and maintain the code.
Let’s add more functionality to our custom hook
import { useState, useCallback } from "react";const cache = new Map();
const defaultOptions = {
cacheKey: "",
refetch: false,
};export const useAsync = (defaultData?: any) => {
const [data, setData] = useState({
data: defaultData ?? null,
error: null,
loading: false,
}); const run = useCallback(async (asyncFn, options = {}) => {
try {
// Merge the default options with the options passed in
const { cacheKey, refetch } = { ...defaultOptions, ...options }; const result = { data: null, error: null, loading: false }; // If we have a cache key and not requesting a new data, then return the cached data
if (!refetch && cacheKey && cache.has(cacheKey)) {
const res = cache.get(cacheKey);
result.data = res;
} else {
setData({ ...result, loading: true });
const res = await asyncFn();
result.data = res;
cacheKey && cache.set(cacheKey, res);
}
setData(result);
return result;
} catch (error) {
const result = { data: null, error: error, loading: false };
setData(result);
return result;
}
}, []); return {
...data,
run,
};
};
import React, { useState, useEffect } from "react";
import { useAsync } from "./hooks";
const Todo = ({ id }) => {
const { data, loading, error, run } = useAsync(null); useEffect(() => {
run(() => fetch(`//jsonplaceholder.typicode.com/todos/${id}`)
.then((res) => res.json()),
{cacheKey: `todo-${id}`});
}, [id]); // Same as above
return ...
};
cacheKey: The key that we will use to store the data in the cache.
refetch: If we want to refetch the data from the API. This is useful when we want to refresh the data in the cache.
NOTE: Cache is available globally, so we can use it in other components. If we use useAsync in multiple components with the same cacheKey, then cache data will be shared across all the components. This is useful when we want to avoid unnecessary API calls if the data is already present in the cache.
React Query and SWR are two popular libraries that can be used to handle all asynchronous data fetching.