DEV Community

Cover image for The 5 React hooks every beginner must know
Gihan Benaragama
Gihan Benaragama

Posted on

The 5 React hooks every beginner must know

A hook is just a special function in React that lets you "hook into" React features from inside a regular function component

In this post

  1. useState — remembering things
  2. useEffect — reacting to changes
  3. useContext — sharing data everywhere
  4. useRef — grabbing DOM elements
  5. useMemo & useCallback — staying fast

Hooks were introduced in React 16.8 and completely changed how we write components. Instead of complex class-based components, we now write simple functions and "hook into" React features. Let's go through the five you'll use every single day.

1. useState

  • Lets your component remember a value — and update the UI when that value changes.

Think of it like a scoreboard. When a player scores, the number on the board updates automatically. useState is your scoreboard — it holds a value, and when it changes, React re-renders the component

// Import the useState Hook from React
import { useState } from 'react'

// Define a functional component named Counter
function Counter() {

  // useState(0):
  // - Creates a state variable called "count"
  // - Creates a function called "setCount" to update "count"
  // - Initializes count with the value 0
  const [count, setCount] = useState(0)

  // Return the JSX (UI) that will be rendered
  return (
    <div>
      {/* Display the current value of count */}
      <p>You clicked {count} times</p>

      {/* 
        When the button is clicked:
        1. Read the current value of count
        2. Add 1 to it
        3. Update the state using setCount()
        4. React re-renders the component with the new value
      */}
      <button onClick={() => setCount(count + 1)}>
        {/* Text shown on the button */}
        Click me
      </button>
    </div>
  )
}

// Export the component so it can be imported into other files
export default Counter

Enter fullscreen mode Exit fullscreen mode

Real-world uses

  • Form inputs - Keep track of what the user is typing into an email or password field.
  • Like button - Store the liked state and toggle it — exactly like a Twitter/X heart button.

Key rule: Never modify state directly (e.g. count = count + 1). Always use the setter function (setCount) so React knows to re-render

2. useEffect — reacting to changes

  • Runs code after your component renders — perfect for fetching data, setting up subscriptions, or talking to the outside world.

Think of it like a notification trigger. When something happens (component loads, a value changes), a side effect kicks in — like fetching fresh data from an API.

// Import the useState and useEffect Hooks from React
import { useState, useEffect } from 'react'

// Define a functional component named WeatherApp
function WeatherApp() {

  // Create a state variable named "weather"
  // Initial value is null because no weather data has been loaded yet
  const [weather, setWeather] = useState(null)

  // useEffect runs after the component renders
  useEffect(() => {

    // Fetch weather data from the API
    fetch('https://api.weather.com/today')

      // Convert the response into JSON format
      .then(res => res.json())

      // Store the fetched data in the weather state
      .then(data => setWeather(data))

  }, []) // Empty dependency array [] means:
          // Run this effect only once when the component first mounts

  // If weather is still null, show a loading message
  if (!weather) {
    return <p>Loading...</p>
  }

  // Once weather data is available, display the temperature
  return (
    <p>It is {weather.temp}°C today</p>
  )
}

// Export the component so it can be used in other files
export default WeatherApp

Enter fullscreen mode Exit fullscreen mode

Avoid infinite loops: If you update state inside useEffect without a dependency array, it will keep running forever. Always add the [] or specific dependencies

3. useContext — sharing data everywhere

  • Lets any component access shared data — without passing props through every level of the tree

  • Think of it like a WiFi network. You set up a router (the context provider) once, and any device in range (any child component) can connect and get the internet — without running a cable from device to device

import { createContext, useContext, useState } from 'react'

// Create a Context object (shared data container)
const ThemeContext = createContext()

function App() {
  // State to store the current theme
  const [theme, setTheme] = useState('light')

  return (
    // Provide theme and setTheme to all child components
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Navbar />
      <Page />
    </ThemeContext.Provider>
  )
}

function Navbar() {
  // Consume (read) values from ThemeContext
  const { theme, setTheme } = useContext(ThemeContext)

  return (
    // Toggle between 'light' and 'dark' themes
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle theme: {theme}
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

Real-world uses

  • Dark mode - Share the current theme across every component without passing it as a prop everywhere.
  • Logged-in user - Once the user logs in, share their name and profile across the entire app.
  1. useRef — grabbing DOM elements
  • Gives you a direct reference to a DOM element — or stores a value that doesn't trigger a re-render when it changes.

Think of it like a sticky note on a physical object. You stick a label on an input box, and later you can point to it directly — without going through React's re-render cycle.

import { useRef } from 'react'

function SearchBar() {
  // Create a ref object. Initially, current is null.
  const inputRef = useRef(null)

  function handleClick() {
    // Access the actual <input> DOM element and place the cursor inside it
    inputRef.current.focus()
  }

  return (
    <div>
      {/* Attach the ref to the input DOM element */}
      <input ref={inputRef} placeholder="Search..." />

      {/* When clicked, call handleClick to focus the input */}
      <button onClick={handleClick}>Focus search</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Real-world uses

  • Auto-focus - Focus a login input the moment a modal opens — better UX without re-rendering.
  • Store a timer ID - Save setTimeout's return value so you can cancel it later — without triggering a re-render.

useState vs useRef: Use useState when changing the value should update the UI. Use useRef when you just need to remember a value or grab a DOM element — no UI update needed.

5. useMemo & useCallback — staying fast

  • Cache expensive calculations or functions so React doesn't redo them on every render.

Think of it like mental notes. If someone asks you "what's 1247 × 38?" you calculate it once and remember the answer. If they ask again with the same numbers — you just give the answer from memory instead of recalculating

import { useState, useMemo } from 'react'

function ProductList({ products }) {
  // State to store the user's search text
  const [search, setSearch] = useState('')

  // Memoize the filtered list and recalculate it
  // only when products or search changes
  const filtered = useMemo(() => {
    return products.filter(p =>
      p.name.toLowerCase().includes(search.toLowerCase())
    )
  }, [products, search])

  return (
    <div>
      {/* Controlled input: value comes from state and updates state on typing */}
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
      />

      {/* Render only the filtered products */}
      {filtered.map(p => (
        <p key={p.id}>{p.name}</p>
      ))}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
import { useCallback } from 'react'

function Parent() {
  // Memoize the function and keep the same function reference
  // between renders unless dependencies change
  const handleClick = useCallback(() => {
    console.log('button clicked!')
  }, []) // [] = create the function once and reuse it on every render

  // Pass the memoized function to the child component
  return <ExpensiveButton onClick={handleClick} />
}
Enter fullscreen mode Exit fullscreen mode
  • useMemo — filtered / sorted lists
    Filtering 10,000 products every keypress is expensive. Cache the result until the data or search term changes.

  • useCallback — stable event handlers
    When passing a function to a child component wrapped in React.memo, keep the same reference to prevent unnecessary re-renders.

Don't over-use these. Wrapping everything in useMemo / useCallback adds its own cost. Only use them when you notice actual performance problems — measure first.

Quick reference summary

  1. useState - Store & update UI values
  2. useEffect - Side effects a fter render
  3. useContext - Share data app-wide
  4. useRef - Access DOM elements
  5. useMemo - Cache expensive values
  6. useCallback - Cache functions

Master these five and you'll be able to build almost any React app. The rest of the hooks exist for more specific or advanced scenarios — but these are the foundation. Happy coding! 🚀

Top comments (0)