Programming

React Hooks: What I Wish Someone Had Told Me When I Started

12 ديسمبر 202515 min read
React Hooks: What I Wish Someone Had Told Me When I Started

After years of writing class components, hooks changed everything. Here's what actually matters and why.

Let Me Be Honest With You

When React Hooks were announced, I was skeptical. I'd spent years mastering class components, understanding lifecycle methods, and suddenly the React team was telling me there's a "better way". But after using hooks extensively in production for 3+ years, I can tell you: they were right. And I want to save you some of the confusion I went through.

So What ARE Hooks, Really?

Here's the thing nobody tells you upfront: hooks are just functions. That's it. They're special functions that let you "hook into" React's internal systems (like state and lifecycle) from regular function components.

Before hooks, if you wanted a component to remember anything (like a counter value, user input, or fetched data), you HAD to use a class component. Function components were "dumb" - they could only display things, not remember things.

Hooks changed that completely.

useState: Your First Hook (And Why It Works The Way It Does)

Let's start with the most common hook. Here's what most tutorials show you:

const [count, setCount] = useState(0);

But let me explain what's ACTUALLY happening here, because understanding this will save you hours of debugging:

  1. useState(0) tells React: "Hey, I need you to store a number for me. Start it at 0."
  2. [count, setCount] is array destructuring. React gives you back two things: the current value, and a function to update it.
  3. Every time you call setCount(newValue), React re-renders your component with the new value.

Now here's the part that trips people up: state updates are asynchronous. Look at this code:

function Counter() {
    const [count, setCount] = useState(0);
    
    function handleClick() {
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        // You might think count is now 3... but it's 1!
    }
}

Why does this happen? Because all three setCount calls are reading the SAME value of count (which is 0). They all set it to 0 + 1 = 1.

The fix? Use the functional update form:

function handleClick() {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    // Now count IS 3, because each update uses the previous value
}

I learned this the hard way on a shopping cart feature. Items kept getting lost because I was updating cart items wrong. Use the prev => newValue pattern whenever your new value depends on the old one.

useEffect: The Most Misunderstood Hook

This is where I see beginners struggle the most. Let me break it down simply.

useEffect is for "side effects" - anything that affects something outside your component:

  • Fetching data from an API
  • Setting up a timer
  • Updating the document title
  • Connecting to a WebSocket

Here's the basic pattern:

useEffect(() => {
    // This code runs AFTER React updates the screen
    document.title = "You clicked " + count + " times";
});

But here's where it gets tricky. That code runs after EVERY render. If you're fetching data, you'll create an infinite loop!

That's why we have the dependency array:

// Run only once when component first appears
useEffect(() => {
    fetchUserData();
}, []); // Empty array = no dependencies = only run once

// Run when userId changes
useEffect(() => {
    fetchUserData(userId);
}, [userId]); // Run again whenever userId changes

Here's my rule of thumb: put every variable from outside the useEffect that you use inside it into the dependency array. ESLint will warn you if you forget.

The Cleanup Function (Don't Skip This!)

If your effect sets up something ongoing (like a timer or subscription), you need to clean it up:

useEffect(() => {
    const timer = setInterval(() => {
        console.log('Tick!');
    }, 1000);
    
    // This function runs when the component is removed
    return () => {
        clearInterval(timer);
        console.log('Timer cleaned up!');
    };
}, []);

I once shipped a bug where I forgot to clean up a WebSocket connection. Every time users navigated between pages, we'd open a NEW connection without closing the old one. After an hour of use, some users had 50+ open connections. Memory usage spiked, and the app crashed. Learn from my mistake!

The Rules of Hooks (And Why They Exist)

React has two strict rules for hooks:

  1. Only call hooks at the top level - Never inside loops, conditions, or nested functions
  2. Only call hooks in React functions - Either in components or custom hooks

Why? Because React tracks hooks by their ORDER. If you call useState inside an if statement, sometimes that hook runs and sometimes it doesn't. React loses track of which state belongs to which hook call, and everything breaks.

My Honest Advice

Start with just useState and useEffect. I know there are a dozen other hooks (useContext, useReducer, useMemo, useCallback, etc.), but these two cover 80% of real-world use cases.

The best way to learn hooks is to build something real. Pick a small project - a todo app, a weather widget, a simple game - and just start writing. You'll make mistakes. That's how you learn.

Good luck, and welcome to the hooks world. Once you get comfortable with them, you won't want to go back to class components. Trust me on that one.

Tags

#React#Hooks#JavaScript#Frontend#Tutorial

Related Posts