Hello! Welcome back to your journey into React.
In our last lesson, we took a significant step by learning how to create the basic structure of a custom hook and adhering to the crucial use naming convention. You successfully extracted the logic for a counter into a useCounter hook, which returned the count value to the component.
That was the "how-to-define" part. Today, we'll focus on making these hooks truly powerful and flexible. A hook that can't communicate back to the component isn't very useful. This lesson is all about that communication.
Our learning outcome is to return values and functions from a custom hook for component use. We'll explore the common patterns for structuring this returned data so that your components can use your custom logic in a clean and intuitive way.
The Purpose of a return Statement in a Hook
Just like any JavaScript function, a hook uses a return statement to send data back to whatever called it. In the useCounter exercise from our previous lesson, you returned a single value: the count.
// A simplified version of your last exercise
function useCounter() {
const [count, setCount] = useState(0);
// ... useEffect logic ...
return count; // Returning a single value
}
function MyComponent() {
const count = useCounter(); // The component receives the value
return <h1>Count: {count}</h1>;
}
This is the simplest pattern, and it's perfect for hooks that provide just one piece of information, like the useOnlineStatus hook from the official React documentation which simply returns a boolean.
However, most of the time, we need more than just one piece of data. We usually need the state and the function(s) to change that state. This is where things get interesting.
Pattern 1: Returning an Array
The most common pattern you'll see is returning an array, which mirrors the behavior of React's built-in useState hook. This pattern typically returns two elements: the current state value and a function to update it.
The main advantage of this pattern is the ease of renaming variables when you destructure them in your component.
Let's watch a video that demonstrates this by building a useLocalStorage hook. This hook will need to return both the value stored in local storage and the function to update that value.
Custom Hooks in React - Every React Developer Should Know This
This video from ByteGrad expertly explains why you'd return an array from a custom hook. It also compares it to the alternative of returning an object, which we will discuss next.
Watch the segment from 16:07 to 19:07. Pay close attention to how the hook returns [value, setValue] and how the component can then destructure it into [theme, setTheme] or [items, setItems].
As the video explained, the key points for returning an array are:
- Flexibility in Naming: You can name the destructured variables anything you like. This is why
useStateis so versatile.// The hook returns [value, setValue] const [name, setName] = useLocalStorage('name'); const [cartItems, setCartItems] = useLocalStorage('cart'); - Order is Critical: The main downside is that the order matters. If the hook returns
[value, setValue]and you destructure it as[setValue, value], you'll introduce bugs that can be hard to track down.
This pattern is so common that you'll see it everywhere. Here is a simple visual recap.

Pattern 2: Returning an Object
The second common pattern is to return a JavaScript object. This approach has its own set of trade-offs.
The primary benefit of returning an object is that the order of properties does not matter, and the code becomes more "self-documenting" because you use the property names defined in the hook.
Let's explore this by looking at a useFetch hook, which needs to manage several pieces of state: the fetched data, a loading status, and any potential errors.
Full React Tutorial #20 - Making a Custom Hook
This tutorial from Net Ninja builds a useFetch hook and chooses to return an object. This is a great example of when an object is a good choice because the hook returns multiple, distinct pieces of information.
First, watch from 04:43 to 05:39 to see why an object is being returned. Then, watch from 06:39 to 08:08 to see how the component destructures this object and even renames a property.
To summarize the object pattern:
- Order is Irrelevant: You can destructure properties in any order you want.
// The hook returns { data, isPending, error } const { isPending, data } = useFetch('/api/users'); - Explicit Naming: You must use the property names defined in the hook. This can make the code clearer but requires an extra step if you need to rename a variable.
// Renaming `data` to `blogs` is more verbose const { data: blogs, isPending } = useFetch('/api/blogs');
The official React documentation also provides an excellent example with a useFormInput hook that returns an object containing the value and onChange props for an input field.
Reusing Logic with Custom Hooks
Let's read a short section from the React docs that implements a useFormInput hook. It shows a practical use case for returning an object of props.
Please read the section titled Custom Hooks let you share stateful logic, not state itself. Scroll down to the Form component example and find the useFormInput custom hook implementation. Notice how it returns an object named inputProps.
A Hybrid Approach
You don't have to choose strictly between an array and an object. Some developers use a hybrid approach that combines the best of both worlds.
A great example is a useBoolean hook, which manages a true/false state.
React: How to create a Custom Hook
This article by Robin Wieruch demonstrates a very clever pattern for a useBoolean hook. It shows how you can return a mix of values and objects.
First, look at the implementation of the useBoolean hook. Notice its return statement: return [state, { setTrue, setFalse, setToggle }]. Then, see how it's used in the App component. This shows a powerful and flexible pattern.
This pattern, [value, { handler1, handler2 }], gives you the easy renaming of the array pattern for the primary value, while grouping all the related updater functions into a convenient, clearly-named object.
Your Turn: Enhance the useCounter Hook
Now it's time to put this into practice. Let's take the useCounter hook from our last lesson and make it more powerful.
Your task is to modify the useCounter hook so that it returns not only the count but also functions to increment, decrement, and reset the count.
Starter Code:
// in useCounter.js
import { useState, useEffect } from 'react';
// This is the hook from the last lesson.
// It currently only returns the count.
export function useCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return count;
}
// in App.js
import { useCounter } from './useCounter';
export default function Counter() {
const count = useCounter();
return (
<div>
<h1>Seconds passed: {count}</h1>
{/* We will add buttons here */}
</div>
);
}
Instructions:
-
Modify
useCounter.js:- The
useEffectthat automatically increments the count every second is no longer needed. You can remove it. - Create three new functions inside your
useCounterhook:increment: Should increase the count by 1.decrement: Should decrease the count by 1.reset: Should set the count back to 0.
- Modify the
returnstatement to return thecountand your three new functions. You can choose to return an array or an object—think about the pros and cons we just discussed.
- The
-
Update
App.js:- Destructure the values and functions returned from your
useCounterhook. - Add three buttons to the JSX: "Increment", "Decrement", and "Reset".
- Connect the
onClickhandler of each button to the corresponding function from your hook.
- Destructure the values and functions returned from your
Your final Counter component should look something like this, allowing the user to control the count manually.
(This is a mock-up of the target UI)
Take your time to implement this. This exercise solidifies everything we've covered about what a custom hook provides back to a component.
Conclusion
Excellent work today! You've moved from simply defining a hook to making it a functional, reusable piece of logic that provides an API for your components.
Key Takeaways:
- A custom hook's
returnstatement is its public API, defining what it provides to components. - Hooks can return any JavaScript type, but the most common patterns are returning an array or an object for state and its updaters.
- Array
[value, setValue]: Great for positional data. Allows easy renaming during destructuring. The order is important. - Object
{ value, setValue }: Great for named data. The order doesn't matter, and it's self-documenting. Renaming is more verbose. - You can choose the pattern that best fits the specific logic of your hook.
Next Up:
We've now built a self-contained, reusable useCounter hook. The final piece of the puzzle is to understand what "reusable" really means. In our next lesson, we will reuse a custom hook across multiple components and see firsthand how React ensures that the logic is shared, but the state is not.