Mastering React Hooks: Practical Guide, Patterns, and Performance Tips
This article explains React Hooks introduced in version 16.8, their backward‑compatible design, how they replace class components, and provides detailed guidance on using useState, useEffect, custom hooks, useReducer, useRef, useMemo, useCallback, and related linting rules with performance‑focused examples.
React Hooks
Notes
Available since React 16.8
Fully backward compatible, optional, and ready to use immediately
Class components are not removed; Hooks are the future‑preferred API
All class‑based lifecycle methods have Hook equivalents (except rarely used error boundaries)
Better TypeScript support and performance because class overhead is avoided
Logic reuse is achieved without higher‑order components or render props.
Basic Example
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Clicked ${count} times`;
});
return (
<div>
<p>Clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
}Hook Rules
Hooks must be called at the top level of a React function component or a custom Hook.
Never call Hooks inside loops, conditions, or nested functions.
The order of Hook calls must be the same on every render.
State Hook – useState
const [state, setState] = useState(initialState);Multiple independent state variables can be declared by calling useState several times. The initial state can be a primitive, object, or lazily computed value.
// Lazy initialization
const [state, setState] = useState(() => {
const initial = expensiveComputation(props);
return initial;
});Effect Hook – useEffect
useEffect(() => {
// side‑effects such as data fetching, subscriptions, DOM updates
return () => {
// optional cleanup
};
}, [dep1, dep2]);useEffect replaces componentDidMount, componentDidUpdate, and componentWillUnmount. The second argument controls when the effect runs; an empty array runs only on mount/unmount.
Custom Hook Example
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}Reducer Hook – useReducer
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter({ initialState }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}Context Hook – useContext
Provides a way to share values like a dispatch function across the component tree without prop drilling.
const TodosDispatch = React.createContext(null);
function TodosApp() {
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
function DeepChild() {
const dispatch = useContext(TodosDispatch);
return <button onClick={() => dispatch({ type: 'add', text: 'hello' })}>Add todo</button>;
}Reference Hook – useRef
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => { /* … */ }, 1000);
intervalRef.current = id;
return () => clearInterval(intervalRef.current);
});
// intervalRef can be read or cleared elsewhere
}Memoization Hooks – useMemo & useCallback
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
const stableCallback = useCallback(() => {
// function body
}, [dep]);Both hooks help avoid unnecessary recomputation or re‑creation of functions between renders.
Imperative Handle – useImperativeHandle
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);Async Data Fetching Example
function SearchResults() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('react');
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
}
fetchData();
return () => { ignore = true; };
}, [query]);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<ul>{data.hits.map(item => <li key={item.objectID}><a href={item.url}>{item.title}</a></li>)}</ul>
</>
);
}Linting – eslint-plugin-react-hooks
The exhaustive-deps rule warns when Hook dependency arrays are incomplete, helping prevent stale closures and bugs.
Performance Tips
Avoid unnecessary re‑renders by memoizing components with React.memo and using useMemo for expensive calculations.
Prefer functional state updates when the new state depends on the previous one.
Use lazy initialization for heavy state setup.
Overall, Hooks enable a more functional, composable, and performant way to write React components while keeping the codebase easier to understand and test.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaoDou Frontend Team
Open-source, innovative, collaborative, win‑win – sharing frontend tech and shaping its future.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
