Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🟢 Basics – React + Next.js Foundation  ·  Lesson 07

useEffect & Side Effects

What is a side effect?

A component's main job is to return JSX. Anything else that reaches outside — starting a timer, reading localStorage, calling an API, changing document.title — is a side effect. useEffect runs such code after React has updated the screen.

Rendercomponent returns JSXScreen updatedbrowser paintsuseEffect runstimer, storage, fetchCleanupbefore next run / unmount
When useEffect runs

useEffect syntax

JSX
'use client'
import { useEffect, useState } from 'react'

useEffect(() => {
  // 1. code that touches the outside world
  return () => {
    // 2. optional cleanup
  }
}, [/* 3. dependencies */])

The dependency array decides when it runs

You writeEffect runsUse for
useEffect(fn, [])Once, after first renderStart a timer, load from storage
useEffect(fn, [q])First render + whenever q changesSearch when text changes
useEffect(fn)After every renderRarely needed — be careful

Example: a live clock with cleanup

JSX – components/Clock.jsx
'use client'
import { useEffect, useState } from 'react'

export default function Clock() {
  const [time, setTime] = useState(null) // null on the server, real time in the browser

  useEffect(() => {
    setTime(new Date())
    const id = setInterval(() => setTime(new Date()), 1000) // start
    return () => clearInterval(id)                            // cleanup: stop
  }, []) // [] = run once after the first render

  return <p>⏰ {time ? time.toLocaleTimeString('en-IN') : '--:--:--'}</p>
}
⏰ 10:42:07 am

Without clearInterval, leaving the page would keep the timer running in the background — a memory leak. The cleanup function prevents that.

💡 We start with null and set the time inside the effect. The server and browser clocks differ, and showing the time directly during render would cause a hydration mismatch warning.

Example: save a note in localStorage

JSX – components/Notes.jsx
'use client'
import { useEffect, useState } from 'react'

export default function Notes() {
  const [text, setText] = useState('')

  // Load once from the browser's storage
  useEffect(() => {
    setText(localStorage.getItem('note') || '')
  }, [])

  // Save every time `text` changes
  useEffect(() => {
    localStorage.setItem('note', text)
  }, [text])

  return <textarea value={text} onChange={(e) => setText(e.target.value)} rows={4} />
}

localStorage exists only in the browser, so it must be used inside useEffect (never at the top of a component, which also runs on the server).

Example: live search with fetch

JSX – components/LiveSearch.jsx
'use client'
import { useEffect, useState } from 'react'

export default function LiveSearch() {
  const [q, setQ] = useState('')
  const [results, setResults] = useState([])

  useEffect(() => {
    if (!q) return setResults([])
    const controller = new AbortController()
    const timer = setTimeout(async () => {          // wait 300 ms after typing stops
      const res = await fetch(`/api/courses?q=${encodeURIComponent(q)}`, { signal: controller.signal })
      const data = await res.json()
      setResults(data.courses)
    }, 300)
    return () => { clearTimeout(timer); controller.abort() } // cancel old request
  }, [q])

  return (
    <div>
      <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search courses" />
      <ul>{results.map((c) => <li key={c.slug}>{c.title} – ₹{c.fee}</li>)}</ul>
    </div>
  )
}
Browser (LiveSearch)Next.js /api/coursestype "py" → wait 300 msGET /api/courses?q=py{ courses: [...] }setResults → re-render
Debounced search: one request after typing stops

In Next.js, prefer the server for data

For data that is part of the page (course lists, blog posts), a Server Component is simpler and better for SEO — the HTML already contains the data and Google can read it:

JSX – app/courses/page.jsx
// app/courses/page.jsx — Server Component: no useEffect, no loading spinner needed
import { searchCourses } from '@/lib/courses'

export default async function CoursesPage({ searchParams }) {
  const { q = '' } = await searchParams
  const list = searchCourses(q)          // runs on the server
  return <ul>{list.map((c) => <li key={c.slug}>{c.title}</li>)}</ul>
}

useEffect + fetch (client)

  • Page first shows empty / spinner
  • Extra request from the browser
  • Search engines may miss content
  • Good for live, user-driven updates

Server Component (await)

  • HTML arrives with data
  • No loading flicker
  • Best for SEO
  • Good for page content
Choose the right place to fetch

Common mistakes

  • Setting state in an effect with no dependency array → infinite render loop.
  • Missing dependencies → effect uses an old value. Include every variable from the component that the effect uses.
  • Making the effect function itself async → not allowed. Create an async function inside and call it.
  • Using window or localStorage outside useEffect → "window is not defined" on the server.

Summary

  • useEffect runs side effects after render.
  • The dependency array controls when it runs; return a cleanup function to undo work.
  • Use it for timers, storage, subscriptions and live search.
  • For page data in Next.js, fetch in Server Components instead.
✅ Practice

Make a component that changes document.title to "(3) New messages" based on a count in state, and resets the title when the component is removed.

Frequently Asked Questions

Why does my useEffect run twice in development?
In development React Strict Mode mounts, unmounts and mounts components again to help you find missing cleanup. It does not happen in production builds.
Should I fetch data with useEffect in Next.js?
Usually no. Fetch data in a Server Component with async/await so the HTML already contains the data. Use useEffect only for browser-only work like timers, localStorage or live search.
What happens if I forget the dependency array?
The effect runs after every render. If the effect also sets state, it can cause an infinite loop.
What is the cleanup function for?
It undoes what the effect started — clears a timer, removes an event listener or cancels a request — before the effect runs again or the component is removed.
← Next.js Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicLists, Keys & Conditional Rendering Your progress: 7 / 37 (19%) · Lesson 8 / 37 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free