I have to be honest that I was getting a bit rusty with React lately, I've been working on mostly backend and CLIs stuff for the past few months, and as I have a very bad memory I tend to forget how things, I used not so long ago, works.
As now I have to work on the front facing part of the application, I need to fetch information from the API and display them, a solution could be to run something like this:
// mycomponent.js
import React, { useEffect, useState} from 'react';
import axios from 'axios';
const MyComponent = () => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState([])
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get('/stuff/to/fetch');
setData(response);
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
}, []);
return (
<div>
{loading && <div>Loading</div>}
{!loading && (
<div>
<h2>Doing stuff with data</h2>
{data.map(item => (<span>{item.name}</span>))}
</div>
)}
</div>
)
}
export default MyComponent;
So essentially we tell the component that when it mounts, it should call the fetchData
function to populate our data
array from the API, and we put some conditions to not show anything while we area loading our data.
The code above is fine as it is, but it stores a bit of logic in the component itself.
If you need to reuse the same logic in another component, that perhaps renders the same data but in a different way, you need to duplicate it, making the code not very DRY.
Custom hooks FTW
I firmly believe that abstraction is always a good way to go, and in my opinion a better solution is to create a custom react hook, where essentially we move the logic of fetching the data to another file, and we make it a reusable hook that can be called from multiple components if needed.
The code for the hook will be something like this:
// use-fetch-data.js
import { useEffect, useState} from 'react';
import axios from 'axios';
const useFetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const { data: response } = await axios.get('/stuff/to/fetch');
setData(response);
} catch (error) {
console.error(error)
}
setLoading(false);
};
fetchData();
}, []);
return {
data,
loading,
};
};
export default useFetchData;
Note: for the sake of keeping the code short I didn't manage the state for errors, relying just on
console.error
.
Now we can refactor our component code, removing all the logic and the states we no longer need, to a much shorter code:
//mycomponent.js
import React from 'react';
import useFetchData from './hooks/use-fetch-data.js'
const MyComponent = () => {
const {
data,
loading,
} = useFetchData();
return (
<div>
{loading && <div>Loading</div>}
{!loading && (
<div>
<h2>Doing stuff with data</h2>
{data.map(item => (<span>{item.name}</span>))}
</div>
)}
</div>
)
}
export default MyComponent;
I hope it will help you understanding better React hooks with this practical example.
Top comments (7)
Cool example! One thing to add that I would find useful is a
status
returned from the custom hook instead of theloading
boolean. This way the status can be success, error or loading and you can also handle Axios errors.Cheers!
That's a really good idea, I just kept it simple for the sake of not writing too much code and just get the gist, but yeah, you are totally right
Thanks for the tips on abstraction. This is exactly where I am in my learning and seeing how you made a custom hook levelled me up :) Cheers!
First, that is great for create a fetch data hooks to get the data, then other pages can re-use it , but i think there can upgrade more, can pass the endpoint url , then that would be a more powerful common fetch data hooks.
if interested,can refer to this article smashingmagazine.com/2020/07/custo...
das ist so praktisch
your last example can't work.
const [loading, setLoading] = useState(false);
otherwise the loading state is always false.
have to be
const [loading, setLoading] = useState(true);
You are right, I probably copied over from an example to another, thanks for that