Hooks are functions that let you “hook into” React state and lifecycle features from function components. They were introduced in React 16.8 to allow function components to have state and side effects without needing to convert to class components.
Why Hooks?
Hooks solve several problems:
Reusable stateful logic : Share logic between components without wrapper hell
Simpler components : No need for class components and this keyword
Better code organization : Group related logic together instead of splitting it across lifecycle methods
Smaller bundle sizes : Function components are smaller than classes
Rules of Hooks
Critical : You must follow these rules when using Hooks, or your component will break:
Only call Hooks at the top level : Don’t call Hooks inside loops, conditions, or nested functions
Only call Hooks from React functions : Call them from function components or custom Hooks, not regular JavaScript functions
According to React’s source code in ReactHooks.js, if these rules are violated, you’ll see an error: Invalid hook call. Hooks can only be called inside of the body of a function component.
// ❌ Don't call Hooks conditionally
function Component () {
if ( condition ) {
const [ state , setState ] = useState ( 0 ); // Wrong!
}
}
// ✅ Call Hooks at the top level
function Component () {
const [ state , setState ] = useState ( 0 );
if ( condition ) {
// Use state here
}
}
Built-in Hooks
React provides several built-in Hooks. Below is a comprehensive overview based on React’s source code.
State Hooks
useState
Adds state to function components:
import { useState } from 'react' ;
function Counter () {
const [ count , setCount ] = useState ( 0 );
return (
< button onClick = { () => setCount ( count + 1 ) } >
Count: { count }
</ button >
);
}
The initial state can be a value or a function:
// Direct value
const [ count , setCount ] = useState ( 0 );
// Function (for expensive calculations)
const [ items , setItems ] = useState (() => {
return loadItemsFromStorage ();
});
useReducer
An alternative to useState for complex state logic:
import { useReducer } from 'react' ;
function reducer ( state , action ) {
switch ( action . type ) {
case 'increment' :
return { count: state . count + 1 };
case 'decrement' :
return { count: state . count - 1 };
default :
return state ;
}
}
function Counter () {
const [ state , dispatch ] = useReducer ( reducer , { count: 0 });
return (
< div >
< p > Count: { state . count } </ p >
< button onClick = { () => dispatch ({ type: 'increment' }) } >
+
</ button >
< button onClick = { () => dispatch ({ type: 'decrement' }) } >
-
</ button >
</ div >
);
}
Effect Hooks
useEffect
Performs side effects in function components:
import { useEffect , useState } from 'react' ;
function UserProfile ({ userId }) {
const [ user , setUser ] = useState ( null );
useEffect (() => {
fetch ( `/api/users/ ${ userId } ` )
. then ( res => res . json ())
. then ( setUser );
}, [ userId ]);
return < div > { user ?. name } </ div > ;
}
useLayoutEffect
Like useEffect, but fires synchronously after DOM mutations:
import { useLayoutEffect , useRef } from 'react' ;
function Tooltip () {
const ref = useRef ();
useLayoutEffect (() => {
// Measure and position before paint
const { height } = ref . current . getBoundingClientRect ();
ref . current . style . marginTop = `- ${ height } px` ;
});
return < div ref = { ref } > Tooltip </ div > ;
}
useInsertionEffect
Fires before all DOM mutations, primarily for CSS-in-JS libraries:
import { useInsertionEffect } from 'react' ;
function useCSS ( rule ) {
useInsertionEffect (() => {
const style = document . createElement ( 'style' );
style . textContent = rule ;
document . head . appendChild ( style );
return () => document . head . removeChild ( style );
}, [ rule ]);
}
Context Hook
useContext
Reads the current value from a context:
import { useContext , createContext } from 'react' ;
const ThemeContext = createContext ( 'light' );
function Button () {
const theme = useContext ( ThemeContext );
return (
< button className = { `btn- ${ theme } ` } >
Themed Button
</ button >
);
}
function App () {
return (
< ThemeContext.Provider value = "dark" >
< Button />
</ ThemeContext.Provider >
);
}
According to React’s source code, calling useContext(Context.Consumer) is not supported and will cause bugs. Always call useContext(Context) instead.
Ref Hook
useRef
Creates a mutable ref object that persists across renders:
import { useRef , useEffect } from 'react' ;
function TextInput () {
const inputRef = useRef ( null );
useEffect (() => {
inputRef . current . focus ();
}, []);
return < input ref = { inputRef } /> ;
}
Refs are also useful for storing mutable values:
function Timer () {
const intervalRef = useRef ( null );
const [ count , setCount ] = useState ( 0 );
const start = () => {
intervalRef . current = setInterval (() => {
setCount ( c => c + 1 );
}, 1000 );
};
const stop = () => {
clearInterval ( intervalRef . current );
};
return (
< div >
< p > { count } </ p >
< button onClick = { start } > Start </ button >
< button onClick = { stop } > Stop </ button >
</ div >
);
}
useMemo
Memoizes a computed value:
import { useMemo } from 'react' ;
function ExpensiveComponent ({ data }) {
const processedData = useMemo (() => {
return data . map ( item => expensiveOperation ( item ));
}, [ data ]);
return < List items = { processedData } /> ;
}
useCallback
Memoizes a function:
import { useCallback } from 'react' ;
function Parent () {
const [ count , setCount ] = useState ( 0 );
const handleClick = useCallback (() => {
setCount ( c => c + 1 );
}, []);
return < Child onClick = { handleClick } /> ;
}
Only use useMemo and useCallback when you have a proven performance problem. They add memory overhead and complexity, so don’t use them prematurely.
Transition Hooks
useTransition
Marks state updates as non-urgent (transitions):
import { useTransition , useState } from 'react' ;
function SearchResults () {
const [ query , setQuery ] = useState ( '' );
const [ results , setResults ] = useState ([]);
const [ isPending , startTransition ] = useTransition ();
const handleChange = ( e ) => {
const value = e . target . value ;
setQuery ( value ); // Urgent update
startTransition (() => {
// Non-urgent update
setResults ( searchData ( value ));
});
};
return (
< div >
< input value = { query } onChange = { handleChange } />
{ isPending && < p > Loading... </ p > }
< List items = { results } />
</ div >
);
}
useDeferredValue
Defers updating a value:
import { useDeferredValue , useState } from 'react' ;
function SearchResults ({ query }) {
const deferredQuery = useDeferredValue ( query );
// This will use the deferred value
const results = useMemo (() => {
return searchData ( deferredQuery );
}, [ deferredQuery ]);
return < List items = { results } /> ;
}
Other Hooks
useId
Generates unique IDs for accessibility attributes:
import { useId } from 'react' ;
function FormField () {
const id = useId ();
return (
<>
< label htmlFor = { id } > Name: </ label >
< input id = { id } />
</>
);
}
useSyncExternalStore
Subscribes to an external store:
import { useSyncExternalStore } from 'react' ;
function useOnlineStatus () {
return useSyncExternalStore (
( callback ) => {
window . addEventListener ( 'online' , callback );
window . addEventListener ( 'offline' , callback );
return () => {
window . removeEventListener ( 'online' , callback );
window . removeEventListener ( 'offline' , callback );
};
},
() => navigator . onLine ,
() => true // Server snapshot
);
}
useImperativeHandle
Customizes the ref value exposed to parent components:
import { useImperativeHandle , useRef , forwardRef } from 'react' ;
const FancyInput = forwardRef (( props , ref ) => {
const inputRef = useRef ();
useImperativeHandle ( ref , () => ({
focus : () => inputRef . current . focus (),
clear : () => inputRef . current . value = ''
}));
return < input ref = { inputRef } /> ;
});
useDebugValue
Displays a label in React DevTools:
import { useDebugValue } from 'react' ;
function useFriendStatus ( friendId ) {
const [ isOnline , setIsOnline ] = useState ( false );
useDebugValue ( isOnline ? 'Online' : 'Offline' );
return isOnline ;
}
Experimental Hooks
These are available in React but may change:
useOptimistic
Shows optimistic state during async operations:
import { useOptimistic } from 'react' ;
function TodoList ({ todos , addTodo }) {
const [ optimisticTodos , addOptimisticTodo ] = useOptimistic (
todos ,
( state , newTodo ) => [ ... state , { ... newTodo , pending: true }]
);
const handleAdd = async ( text ) => {
addOptimisticTodo ({ id: Date . now (), text });
await addTodo ( text );
};
return (
< ul >
{ optimisticTodos . map ( todo => (
< li key = { todo . id } style = { { opacity: todo . pending ? 0.5 : 1 } } >
{ todo . text }
</ li >
)) }
</ ul >
);
}
useActionState
Manages state for form actions:
import { useActionState } from 'react' ;
function Form () {
const [ state , formAction , isPending ] = useActionState (
async ( previousState , formData ) => {
const response = await submitForm ( formData );
return response ;
},
null
);
return (
< form action = { formAction } >
< input name = "text" />
< button disabled = { isPending } > Submit </ button >
{ state ?. message && < p > { state . message } </ p > }
</ form >
);
}
use
Reads the value of a Promise or Context:
import { use } from 'react' ;
function UserName ({ userPromise }) {
const user = use ( userPromise );
return < p > { user . name } </ p > ;
}
Complete Hook List from React Source
Based on ReactHooks.js, here are all available hooks:
useState - State management
useReducer - Complex state management
useRef - Mutable refs
useEffect - Side effects
useLayoutEffect - Synchronous side effects
useInsertionEffect - CSS injection
useContext - Read context
useCallback - Memoize functions
useMemo - Memoize values
useImperativeHandle - Customize ref
useDebugValue - DevTools label
useTransition - Non-urgent updates
useDeferredValue - Defer value updates
useId - Unique IDs
useSyncExternalStore - External store subscription
useOptimistic - Optimistic UI
useActionState - Form actions
use - Read promises/context
Custom Hooks
You can create your own Hooks to reuse stateful logic:
import { useState , useEffect } from 'react' ;
// Custom Hook
function useWindowSize () {
const [ size , setSize ] = useState ({
width: window . innerWidth ,
height: window . innerHeight
});
useEffect (() => {
function handleResize () {
setSize ({
width: window . innerWidth ,
height: window . innerHeight
});
}
window . addEventListener ( 'resize' , handleResize );
return () => window . removeEventListener ( 'resize' , handleResize );
}, []);
return size ;
}
// Usage
function Component () {
const { width , height } = useWindowSize ();
return < p > Window is { width } x { height } </ p > ;
}
Custom Hooks must start with “use” (like useWindowSize, useFetch, etc). This convention is important because it lets React check for Hook rule violations.
Common Patterns
Fetching Data
function useData ( url ) {
const [ data , setData ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
const [ error , setError ] = useState ( null );
useEffect (() => {
setLoading ( true );
fetch ( url )
. then ( res => res . json ())
. then ( data => {
setData ( data );
setLoading ( false );
})
. catch ( err => {
setError ( err );
setLoading ( false );
});
}, [ url ]);
return { data , loading , error };
}
function useForm ( initialValues ) {
const [ values , setValues ] = useState ( initialValues );
const handleChange = ( e ) => {
setValues ({
... values ,
[e.target.name]: e . target . value
});
};
const reset = () => setValues ( initialValues );
return { values , handleChange , reset };
}
Next Steps
Rendering Learn how React renders and updates the DOM
Lifecycle Understand component lifecycle in depth