A custom Hook lets you reuse stateful logic across components, but it does not share the state itself. Each call to the same Hook creates its own independent values, which is exactly what makes the reuse safe. React re-runs the Hook body once per call, as if the logic were written inline in each component.
Show the logic you want to share
This component tracks a name field with its own state and a change handler. An email field would repeat the same pattern.
import { useState } from "react";
function Form() {
const [name, setName] = useState("");
function handleNameChange(event) {
setName(event.target.value);
}
return (
<label>
Name <input value={name} onChange={handleNameChange} />
</label>
);
}Every new field needs the same three pieces: a state variable, a handler, and a value binding. Only the label text and the state name differ.
Extract a Hook for one field
Move the state and the handler into a Hook that returns everything one input needs.
import { useState } from "react";
function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(event) {
setValue(event.target.value);
}
return [value, handleChange];
}The useInput Hook contains the stateful part of the form. It returns the current value and the handler that updates it, so a component never touches useState directly for that field.
Two calls, two independent states
Call the Hook once per field. Each call gets its own state variable.
function Form() {
const [name, handleNameChange] = useInput("");
const [email, handleEmailChange] = useInput("");
return (
<div>
<label>Name <input value={name} onChange={handleNameChange} /></label>
<label>Email <input value={email} onChange={handleEmailChange} /></label>
</div>
);
}Typing in the name field changes only the name state. The email field is separate. There is no shared variable hiding behind the two calls.
What is shared and what is not
- Shared: the update logic, the return shape, and the behavior.
- Not shared: the values themselves, including state and Effects.
React runs the Hook body once per call, so two calls behave like two independent copies of the same code. An Effect inside a custom Hook is duplicated the same way, so each call site gets its own subscription or timer.
Pass inputs to customize the logic
Arguments let each call configure the same behavior. useInput accepts an initial value, and a Hook can take any inputs, such as a fetch URL or a delay. The inputs also feed the Hook's dependency arrays, so a change re-runs the logic with fresh values.
When you really need shared state
If two components must reflect the same value, lift the state up to their common parent and pass it down as props. A custom Hook cannot turn two calls into one shared variable. Lifting state keeps one source of truth, so both components read and update the same value without drifting.
Use a Hook to share behavior, and use lifting state up to share values. See how to create a custom Hook for the extraction steps.
Rune AI
Key Insights
- A custom Hook shares stateful logic, not state itself.
- Each call to the same Hook creates independent state.
- Pass arguments to configure behavior per call site.
- Lift state up when components need the same value.
- Use Hooks for behavior and props for shared values.
Frequently Asked Questions
Does a custom Hook share state between components?
How do I share actual state between components?
Can I pass arguments to a custom Hook?
Conclusion
Custom Hooks reuse stateful logic, not state itself. Every call is independent, which keeps the reuse safe. When two components need the same value, lift the state up and pass it down as props.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.