React State & useEffect Hook
Side Effects, Timers, and Cleanup
By AI Learning Assistant · React · useState · useEffect · Cleanup
🤖 Your Kind AI Learning Assistant
Welcome to Day 23! Today we're mastering useState and useEffect — two of the most important React Hooks. Don't worry if useEffect seems confusing at first. I'll explain it step by step, with simple analogies and practical examples. By the end, you'll understand side effects, cleanup functions, and how to avoid infinite re-renders. Let's go! 🚀
What Are Side Effects?
Anything React Can't Handle Automatically
React is great at rendering UI based on state and props. But what about things that aren't rendering? Things like:
- Fetching data from an API (getting data from a server)
- Setting up a timer or interval (like a countdown clock)
- Subscribing to WebSocket connections (real-time updates)
- Changing the document title manually
- Saving data to localStorage
- Adding event listeners to the window or document
These are called side effects because they happen "outside" React's normal rendering flow. React can't automatically handle them, so it gives us a special Hook: useEffect.
🎯 Side Effects Analogy — A Restaurant
Imagine you're a chef. Your main job is cooking meals (rendering UI). But there are other things you need to do: order ingredients (fetch data), set a timer for the oven (setInterval), wash dishes (cleanup). These are "side effects" — they're not cooking, but they're necessary for the kitchen to run smoothly. React's useEffect is like your assistant who handles all these side effects for you!
The useEffect Hook — Step by Step
Run Code After Render · Dependency Array
useEffect is a Hook that lets you perform side effects in your components. It has a simple structure:
useEffect(() => {
// This code runs AFTER the component renders
// This is where you put your side effect code
return () => {
// OPTIONAL: This is the CLEANUP function
// Runs before component unmounts or before effect runs again
};
}, [dependencies]); // Dependency array controls WHEN the effect runs
Three important things to understand:
1. The Effect Function — This is where your side effect code lives. It runs after React has updated the DOM.
2. The Cleanup Function (Optional) — If your effect creates something that needs to be cleaned up (like a timer, subscription, or event listener), you return a cleanup function. React runs this cleanup before the component unmounts OR before the effect runs again.
3. The Dependency Array (Optional) — This tells React when to re-run the effect. There are three patterns:
- No dependency array: Effect runs after EVERY render (rarely used — can cause performance issues)
- Empty array []: Effect runs ONLY ONCE after the first render (like componentDidMount)
- Array with dependencies [state, prop]: Effect runs on first render AND whenever any dependency changes
Build a Live Course Countdown Timer
Practical useEffect Project · Step by Step
Let's build a real project together! We'll create a countdown timer that starts when the user clicks a button and counts down from 30 days. This project will teach you:
- Multiple useState variables
- setInterval for timers
- The useEffect cleanup function to prevent memory leaks
- Conditional effect execution with dependency arrays
📜 Complete src/App.js — Step by Step Commented
// ============================================ // COUNTDOWN TIMER WITH useEffect // ============================================ // Import the necessary Hooks from React import { useState, useEffect } from "react"; function App() { // ============================================ // STEP 1: Set up state variables // ============================================ // days: How many days are left in the countdown const [days, setDays] = useState(30); // started: Whether the countdown has started const [started, setStarted] = useState(false); // ============================================ // STEP 2: Set up the useEffect for the timer // ============================================ useEffect(() => { // If the countdown hasn't started OR we're at 0, exit early if (!started) return; if (days === 0) return; // Create the timer — runs every 1000ms (1 second) const timer = setInterval(() => { setDays(prev => prev - 1); }, 1000); // ============================================ // STEP 3: The CLEANUP function — CRITICAL! // ============================================ // Runs when: component unmounts OR before effect runs again // Without this, the timer runs FOREVER! return () => { clearInterval(timer); }; }, [started, days]); // ============================================ // STEP 4: Render the UI // ============================================ const daysColor = days > 10 ? "green" : "red"; return ( <div style={{ padding: "40px", textAlign: "center" }}> <h1>JavaScript Mastery Countdown</h1> <h2 style={{ fontSize: "80px", color: daysColor }}>{days}</h2> <p>days remaining</p> <button onClick={() => setStarted(true)} disabled={started} > {started ? "Countdown in progress..." : "Start Countdown"} </button> </div> ); } export default App;
✨ Live Interactive Demo
JavaScript Mastery Countdown
30days remaining
Click Start to see the countdown in action. This is exactly what your React app will do.
Why Do We Need a Cleanup Function?
Prevent Memory Leaks · Stop Running Processes
The cleanup function is one of the most important — and most misunderstood — parts of useEffect. Without it, your app can develop memory leaks, where old timers, subscriptions, or event listeners keep running even after they're no longer needed, slowly consuming more and more memory until your app crashes.
🎯 What Happens Without Cleanup? — A Scary Story
Imagine you set a timer that runs every second. The user navigates away from the page, but the timer keeps running. Every second, it tries to update a component that no longer exists. This wastes CPU, battery, and memory. Worse, if the user comes back and starts another timer, now you have TWO timers running. Leave and return 100 times? 100 timers running simultaneously! Your app will become slower and slower until it crashes. The cleanup function prevents this by stopping the timer when the component unmounts.
💬 Ask Your Kind AI Assistant
Deep Dive: What is the useEffect Cleanup Function?
🗣️ You Ask the AI:
"What is the useEffect cleanup function in React and when do I need it? Show me 3 real examples of memory leaks it prevents."
🤗 Your Kind AI Assistant Responds:
What is the cleanup function?
The cleanup function is an optional function you can return from your useEffect. React runs this cleanup in two situations:
1. When the component unmounts — The user navigates away, and React needs to clean up any leftover resources.
2. Before the effect runs again — When dependencies change, React runs cleanup from the previous effect before running the new one.
When do you need a cleanup function?
- setInterval or setTimeout timers
- Event listeners (window.addEventListener)
- WebSocket or API subscriptions
- Animation frames (requestAnimationFrame)
- Manual DOM manipulations that need reverting
Real Example 1: Timer Memory Leak
// ❌ WITHOUT CLEANUP — MEMORY LEAK! useEffect(() => { setInterval(() => console.log("Still running!"), 1000); }, []); // Runs FOREVER even after unmount // ✅ WITH CLEANUP — PROPERLY CLEANED useEffect(() => { const timer = setInterval(() => { console.log("Running only while mounted"); }, 1000); return () => clearInterval(timer); }, []);Real Example 2: Event Listener Memory Leak
// ❌ WITHOUT CLEANUP — MEMORY LEAK! useEffect(() => { const fn = () => console.log(window.innerWidth); window.addEventListener('resize', fn); }, []); // Listener stays FOREVER // ✅ WITH CLEANUP — PROPERLY CLEANED useEffect(() => { const fn = () => console.log(window.innerWidth); window.addEventListener('resize', fn); return () => window.removeEventListener('resize', fn); }, []);Real Example 3: WebSocket Subscription Leak
// ❌ WITHOUT CLEANUP — Connection stays open! useEffect(() => { const ws = new WebSocket('wss://api.example.com'); ws.onmessage = (msg) => console.log(msg.data); }, []); // ✅ WITH CLEANUP — PROPERLY CLOSES useEffect(() => { const ws = new WebSocket('wss://api.example.com'); ws.onmessage = (msg) => console.log(msg.data); return () => ws.close(); }, []);The Golden Rule: If you create something in useEffect that needs to be destroyed or stopped, you MUST provide a cleanup function. Otherwise, you're creating a memory leak that will degrade performance over time. The cleanup function is your insurance policy against slow, crashy apps!
📝 Your Learning Journal
Today I learned that side effects are things React can't handle automatically — like timers, API calls, and event listeners. useEffect is the Hook that manages these side effects. The dependency array controls when effects run: empty array = once on mount, with dependencies = whenever dependencies change. The cleanup function is CRITICAL for preventing memory leaks — it stops timers, removes event listeners, and closes connections when components unmount. I built a countdown timer that actually works! The setInterval combined with cleanup finally makes sense. 🎯
Dependency Array Patterns
Every Render · Once · On Dependency Change
Pattern Syntax When It Runs Common Use Case No dependency array useEffect(() => {}) After EVERY render Rarely used — usually causes performance issues Empty array useEffect(() => {}, []) Once after first render Initial data fetch, setting up subscriptions With dependencies useEffect(() => {}, [count]) First render + on change Save to localStorage, API calls on prop change ⚠️ Common Mistake: Infinite Re-Render Loop
What causes infinite loops? If you put state in the dependency array AND update that same state inside the effect WITHOUT a condition to stop it, you get an infinite loop!
// ❌ THIS CAUSES AN INFINITE LOOP! useEffect(() => { setCount(count + 1); // Updates count → re-render → effect runs again → infinite! }, [count]); // ✅ FIX: Add a condition useEffect(() => { if (count < 10) setCount(count + 1); }, [count]); // ✅ FIX: Empty array if you only want to run once useEffect(() => { setCount(10); }, []); // Runs once, sets count to 10, no infinite loop🤗 Your Kind AI Assistant — Always Here to Help
💬 Ask me anything! "Why is my countdown going negative?" → I'll help you add a condition to stop when days reach 0.
💬 Stuck on an infinite loop? "My component keeps re-rendering forever!" → I'll help you identify which state is causing the loop.
💬 Ready for a challenge? "How would I add a pause and resume button to the countdown?" → I'll guide you through adding more state and conditional timer logic.
You've mastered useState and useEffect! These are the two most important React Hooks. With these, you can build timers, fetch data, handle subscriptions, and much more. Celebrate your progress — you're becoming a React developer! 🌟
AI-Assisted JavaScript Learning · React State & useEffect · Side Effects, Timers, and Cleanup
Learn this with a real instructor
Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.