React’s custom hooks are a powerful way to organize reusable logic. In this article, we’ll create a custom hook called useTodos
that not only fetches todos but also auto-refreshes them at a given interval.
We’ll also highlight one of the trickiest parts of useEffect
: dependencies.
Why Use a Custom Hook Instead of Fetching in App
?
If we fetched todos directly in App
:
- The component would mix UI logic and data-fetching logic, making it harder to read.
- Reusing the fetching logic in another component would mean copy-pasting code.
- Managing intervals and cleanup would clutter the component.
By moving the fetching + interval logic into a custom hook, we get:
✔ Reusability
✔ Cleaner code
✔ Automatic cleanup
✔ Better separation of concerns
The Custom Hook with Interval Refresh
import { useEffect, useState } from "react";
import axios from "axios";
function useTodos(timeout) {
const [todos, setTodos] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Function to fetch todos
const fetchTodos = () => {
axios.get("https://dummyjson.com/todos").then((res) => {
setTodos(res.data.todos);
setLoading(false);
});
};
// Fetch once when the component mounts
fetchTodos();
// Set up the interval to refresh data
const reRunning = setInterval(fetchTodos, timeout * 1000);
// Cleanup the interval when component unmounts OR timeout changes
return () => clearInterval(reRunning);
}, [timeout]); // 👈 dependencies array
return { todos, loading };
}
Why [timeout]
as a Dependency?
The second argument of useEffect
— the dependencies array — decides when React should re-run the effect.
- If we leave it empty
[]
, the effect runs only once on mount. That means iftimeout
changes later, the interval wouldn’t update. - By including
[timeout]
, we’re telling React: 👉 “Whenevertimeout
changes, tear down the old interval and set up a new one.”
This ensures the auto-refresh logic always respects the latest interval value.
Using the Hook in a Component
function App() {
const { todos, loading } = useTodos(6); // refresh every 6 seconds
if (loading) {
return <p>Loading...</p>;
}
return (
<>
{todos.map((todo) => (
<Track todo={todo} key={todo.id} />
))}
</>
);
}
Here, App
is clean:
- No
axios
calls - No
setInterval
mess - Just pure rendering logic
Displaying Each Todo
function Track({ todo }) {
return (
<div className="font-black">
{todo.id} - {todo.todo}
</div>
);
}
Why Dependencies Matter So Much
Dependencies in useEffect
are like a watch list. React monitors them, and if any value changes:
- React cleans up the old effect (important for intervals, subscriptions, listeners, etc.).
- React runs the effect again with the new values.
👉 Without [timeout]
, your interval would stay fixed to the old value, even if the parent passed a new one.
👉 With [timeout]
, the hook dynamically adapts, keeping your app flexible and bug-free.
Final Thoughts
This custom hook shows how to combine:
- Data fetching
- Auto-refresh with intervals
- Proper cleanup
- Dynamic dependency handling
The takeaway is:
-
Empty
[]
dependency → run once on mount. -
Dynamic dependencies
[timeout]
→ re-run when values change.
Top comments (0)