React Hooks
React hooks are a new addition in React 16.8 that lets you use state and other React features without writing a class.
Motivation
Before Hooks, there was no way to use state or lifecycle methods in a functional component. You had to convert your functional component to a class component if you needed to use state or lifecycle methods. This could make your code harder to follow and maintain.
Hooks solve this problem by letting you use state and other React features in a functional component. This makes your code easier to read and maintain.
Rules of Hooks
There are a few rules that you need to follow when using hooks in React:
- Only call hooks at the top level of a functional component or custom hook.
- Only call hooks from React function components or custom hooks.
- Don't call hooks from regular JavaScript functions.
- Don't call hooks inside loops, conditions, or nested functions.
Following these rules will ensure that your hooks work correctly and that your code is easy to understand.
Built-in Hooks
React comes with a few built-in hooks that you can use to add state and other features to your functional components.
useState
The useState
hook lets you add state to a functional component. It takes an initial state as an argument and returns an array with two elements: the current state and a function to update the state.
import React, { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
In this example, we use the useState
hook to add a count state to the Counter
component. We initialize the count state to 0
and use the setCount
function to update the count state when the button is clicked.
useEffect
The useEffect
hook lets you perform side effects in a functional component. It takes a function as an argument and runs that function after every render.
import React, { useState, useEffect } from 'react'
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `Count: ${count}`
})
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
In this example, we use the useEffect
hook to update the document title with the count state after every render.
useRef
The useRef
hook lets you create a mutable object that persists for the lifetime of the component. It returns a mutable ref object with a current
property that you can use to store values.
import React, { useRef } from 'react'
function TextInput() {
const inputRef = useRef()
const focusInput = () => {
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef} type='text' />
<button onClick={focusInput}>Focus Input</button>
</div>
)
}
In this example, we use the useRef
hook to create a ref object that stores a reference to the input element. We use the focusInput
function to focus the input element when the button is clicked.
useContext
The useContext
hook lets you access the value of a context provider in a functional component. It takes a context object as an argument and returns the current context value.
import React, { useContext } from 'react'
const ThemeContext = React.createContext('light')
function ThemeButton() {
const theme = useContext(ThemeContext)
return <button style={{ background: theme }}>Click Me</button>
}
In this example, we use the useContext
hook to access the value of the ThemeContext
provider in the ThemeButton
component. The theme
variable will contain the current theme value.
useReducer
The useReducer
hook lets you manage complex state logic in a functional component. It takes a reducer function and an initial state as arguments and returns the current state and a dispatch function.
import React, { useReducer } from 'react'
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:
return state
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
)
}
In this example, we use the useReducer
hook to manage the count state in the Counter
component. We define a reducer function that handles the increment
and decrement
actions, and use the dispatch
function to dispatch actions to the reducer.
useCallback
The useCallback
hook lets you memoize a function so that it only changes when its dependencies change. It takes a function and an array of dependencies as arguments and returns a memoized version of the function.
import React, { useState, useCallback } from 'react'
function Counter() {
const [count, setCount] = useState(0)
const increment = useCallback(() => {
setCount(count + 1)
}, [count])
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
)
}
In this example, we use the useCallback
hook to memoize the increment
function so that it only changes when the count
state changes. This can help optimize performance by preventing unnecessary re-renders.
useMemo
The useMemo
hook lets you memoize a value so that it only changes when its dependencies change. It takes a function and an array of dependencies as arguments and returns a memoized version of the value.
import React, { useState, useMemo } from 'react'
function Counter() {
const [count, setCount] = useState(0)
const squaredCount = useMemo(() => count * count, [count])
return (
<div>
<p>{count}</p>
<p>{squaredCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
In this example, we use the useMemo
hook to memoize the squaredCount
value so that it only changes when the count
state changes. This can help optimize performance by preventing unnecessary re-renders.
Advanced Hooks
React also provides some advanced hooks that you can use to build more complex components.
useImperativeHandle
The useImperativeHandle
hook lets you customize the instance value that is exposed to parent components when using ref
. It takes a ref object and a function as arguments and returns a custom instance value.
import React, { useRef, useImperativeHandle } from 'react'
function FancyInput(props, ref) {
const inputRef = useRef()
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
}))
return <input ref={inputRef} />
}
FancyInput = React.forwardRef(FancyInput)
In this example, we use the useImperativeHandle
hook to customize the instance value that is exposed to parent components when using ref
. We define a focus
method that focuses the input element and return it as the custom instance value.
useLayoutEffect
The useLayoutEffect
hook is similar to useEffect
, but it runs synchronously after the DOM has been updated. This can be useful for measuring elements or performing other DOM operations that require the layout to be up-to-date.
import React, { useState, useLayoutEffect } from 'react'
function MeasureElement() {
const [width, setWidth] = useState(0)
const ref = useRef()
useLayoutEffect(() => {
setWidth(ref.current.offsetWidth)
})
return <div ref={ref}>Width: {width}</div>
}
In this example, we use the useLayoutEffect
hook to measure the width of an element after the DOM has been updated. We use a ref to store a reference to the element and update the width state with the element's offsetWidth
.
useDebugValue
The useDebugValue
hook lets you display a label for custom hooks in React DevTools. It takes a value and a formatter function as arguments and displays the formatted value in React DevTools.
import { useDebugValue } from 'react'
function useCustomHook() {
const value = 'Hello, world!'
useDebugValue(value, (value) => `Value: ${value}`)
return value
}
In this example, we use the useDebugValue
hook to display a label for the useCustomHook
custom hook in React DevTools. We provide a formatter function that formats the value as Value: ${value}
.
useId
The useId
hook generates a unique ID that can be used to associate form elements with their labels. It takes an optional prefix as an argument and returns a unique ID.
import { useId } from 'react'
function TextInput({ label }) {
const id = useId('text-input')
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} type='text' />
</div>
)
}
useDeferredValue
The useDeferredValue
hook lets you defer updating a value until the next render. This can be useful for optimizing performance by batching updates to expensive computations.
import { useState, useDeferredValue } from 'react'
function ExpensiveComponent() {
const [count, setCount] = useState(0)
const deferredCount = useDeferredValue(count, { timeoutMs: 1000 })
return (
<div>
<p>Count: {deferredCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
In this example, we use the useDeferredValue
hook to defer updating the deferredCount
value until the next render. We provide a timeoutMs
option to specify the delay in milliseconds before updating the value.
useTransition
The useTransition
hook lets you add transitions to a component when updating state. It takes an optional config object as an argument and returns a startTransition
function that you can use to start a transition.
import { useState, useTransition } from 'react'
function TransitionComponent() {
const [count, setCount] = useState(0)
const [isPending, startTransition] = useTransition()
const handleClick = () => {
startTransition(() => {
setCount(count + 1)
})
}
return (
<div>
<p>{count}</p>
<button onClick={handleClick} disabled={isPending}>
Increment
</button>
</div>
)
}
In this example, we use the useTransition
hook to add a transition to the TransitionComponent
component. We use the startTransition
function to start a transition when the button is clicked, and disable the button while the transition is pending.
useSyncExternalStore
The useSyncExternalStore
hook lets you synchronize state between a React component and an external store. It takes a store object and a sync function as arguments and synchronizes the component state with the external store.
import { useState, useSyncExternalStore } from 'react'
function SyncComponent() {
const [count, setCount] = useState(0)
const store = {
get: () => count,
set: (value) => setCount(value),
}
useSyncExternalStore(store, {
sync: (store) => {
const value = store.get()
// Sync with external store
},
})
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
In this example, we use the useSyncExternalStore
hook to synchronize the count
state with an external store. We define a store
object with get
and set
methods to get and set the state value, and provide a sync
function to sync the state with the external store.
Custom Hooks
You can also create your own custom hooks to reuse stateful logic across multiple components.
import { useState } from 'react'
function useCounter(initialCount = 0) {
const [count, setCount] = useState(initialCount)
const increment = () => {
setCount(count + 1)
}
const decrement = () => {
setCount(count - 1)
}
return { count, increment, decrement }
}
In this example, we create a custom useCounter
hook that adds count state to a component and provides functions to increment and decrement the count state.
Conclusion
React Hooks are a powerful addition to React that lets you use state and other React features in a functional component. They make your code easier to read and maintain by removing the need for class components. You can use the built-in hooks like useState
and useEffect
to add state and side effects to your components, and create custom hooks to reuse stateful logic across multiple components.
I hope this article has given you a good introduction to React Hooks and how you can use them in your projects.
Happy coding! 🚀