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:

  1. Only call hooks at the top level of a functional component or custom hook.
  2. Only call hooks from React function components or custom hooks.
  3. Don't call hooks from regular JavaScript functions.
  4. 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! 🚀