close icon
daily.dev platform

Discover more from daily.dev

Personalized news feed, dev communities and search, much better than whatโ€™s out there. Maybe ;)

Start reading - Free forever
Start reading - Free forever
Continue reading >

React Hooks Explained Simply

React Hooks Explained Simply
Author
Nimrod Kramer
Related tags on daily.dev
toc
Table of contents
arrow-down

๐ŸŽฏ

Learn about React Hooks, how they simplify code, manage state, and share logic in function components. Explore common pitfalls, core hooks, rules, refactoring tips, and best practices for using React Hooks effectively.

React Hooks are tools that allow you to use state and other React features without writing class components. They're designed to simplify your code and make it easier to share logic across components. Here's a quick overview of what you need to know about React Hooks:

  • Simplify Your Code: Avoid the complexity of class components.
  • Reusable Logic: Easily share and reuse stateful logic.
  • Built-in Hooks: Such as useState, useEffect, and useContext for managing state, side effects, and context.
  • Custom Hooks: Create your own hooks for custom reusable logic.
  • Rules of Hooks: Use them at the top level of your components and only in React functions.
  • Common Pitfalls: Understand common issues and how to avoid them, like stale closures in useEffect.
  • Refactoring: How to convert class components to function components using hooks.
  • Best Practices: Tips for using hooks effectively, like keeping them small and focused.

Whether you're a beginner or an experienced developer, understanding and applying React Hooks can greatly improve your React applications by making your code cleaner, more modular, and easier to understand.

Class Component Challenges

Class components in React are strong tools but they can make things like keeping track of changes and sharing code a bit tough:

  • Complex lifecycle methods - Components have a bunch of steps like componentDidMount, componentDidUpdate, and more. Keeping up with all these can be tricky.
  • Difficult to reuse logic - If you want to share code that deals with state between components, you usually need complex patterns. These can make your components harder to understand.
  • Confusing 'this' binding - Classes sometimes make using this a headache, making it hard to keep track of what it refers to.

The Introduction of Hooks

Hooks

React Hooks were made to tackle these issues, letting you handle state and other React features without needing classes:

  • Hooks let you manage state and more without classes, which means you don't have to deal with the confusing parts of this.
  • You can create custom hooks to split out logic for things like state, effects, and context. This makes it much easier to use the same code in different places.
  • You don't have to juggle lifecycle methods anymore. Things like useEffect take care of that for you.

Overall, Hooks give you a simpler and more flexible way to work with React. By moving away from complex patterns, they help make your code easier to read and share. This means you can reuse code more easily and keep your components neat.

What Are React Hooks?

React Hooks are like special tools that let you add features such as keeping track of data (state) and doing things when your component loads or updates, all from within function components. This means you can do a lot without needing to write class components.

Hereโ€™s what you should know about React Hooks:

  • They are just functions - But they have to follow two rules:
  • You can only use them inside function components.
  • You have to use them at the top of your component, not inside loops or conditions.
  • They let function components have their own state - With the useState hook, your components can remember things and change what they show based on that memory.
  • You can share logic between components - By making your own hooks, you can take some logic about state out of your components and use it elsewhere. This is handy for things like fetching data.
  • They replace the old ways of doing things when your component loads or updates - The useEffect hook helps you do stuff like fetching data or setting up subscriptions, similar to how you might have used componentDidMount or componentDidUpdate in class components.

In short, React Hooks let you do more with function components, like keeping track of data and reacting to changes. They make it easier to share logic across different parts of your app and simplify how you work with React. This means less repeating yourself and clearer, simpler code.

Core React Hooks Explained

React Hooks let you use state and other React features in function components. This means you can do things like keep track of data or perform actions at certain times without writing class components. Here are three main Hooks you should know about:

useState

The useState Hook lets you keep track of data in your components. Here's how you use it:

import { useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0); 
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

In this example, useState(0) starts the count at 0. The setCount function updates it. useState gives back an array with the current state and a function to change it, which is why we use array destructuring to get count and setCount.

useEffect

The useEffect Hook lets you do things after your component renders. For example, you can fetch data, set up subscriptions, or manually change the DOM. Here's how you might fetch data:

import { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(data => setData(data));
  }, []);

  // ...
}

useEffect needs two things: a function to run and an array of values it depends on. If the array is empty [], it means the effect runs only once when the component is first shown and when it goes away.

useContext

The useContext Hook lets you use context in your components. Context is a way to pass data through your component tree without having to pass props down manually at every level.

Here's a simple example:

const ThemeContext = React.createContext('light');

function MyComponent() {
  const theme = useContext(ThemeContext); 
  return <p>The theme is {theme}</p>;
}

With useContext, MyComponent can use the theme value from context easily.

These three Hooks - useState, useEffect, and useContext - are key for managing state, side effects, and context in your function components. They make your code simpler and more straightforward.

Additional Hooks

React has more handy hooks beyond useState and useEffect that help you do even more cool stuff in your components. Let's go over a few more that are super useful:

useReducer

Think of useReducer as a more powerful version of useState, perfect for when your component's state gets complex. It works well for states that have multiple parts or when the new state depends on the old one.

Here's a peek at how useReducer works:

const [state, dispatch] = useReducer(reducer, initialState);

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {...state, count: state.count + 1};
    case 'decrement':
      return {...state, count: state.count - 1};
    default: 
      return state;
  }
}

useRef

useRef gives you a way to hang onto something (like a DOM element) that doesn't get wiped out when your component re-renders. It's like keeping a sticky note on something you don't want to lose.

Example time:

const inputEl = useRef(null);

<input ref={inputEl} type="text"/>;

// To get the DOM node:
inputEl.current;  

useMemo

useMemo is like a brainy assistant that remembers the result of a heavy-duty function so you don't have to run it again unless absolutely necessary. It helps keep things running smoothly and quickly.

Example:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback

useCallback keeps your functions remembered so they don't get recreated every time your component re-renders. This is especially handy for functions that need to stick around, like event handlers.

Here's how it looks:

const handleClick = useCallback(() => {
  // Something to do here
}, []); 

While there are even more hooks like useImperativeHandle, useDebugValue, and useTransition for special situations, the ones we've talked about are the big players. They make your components smarter and your code cleaner, without the need for classes. Getting the hang of these hooks will equip you with everything you need for most things you'll do in React.

Rules of Hooks

When you're using React Hooks like useState, useEffect, or any others, there are two important rules you need to follow to avoid problems:

  • Only Call Hooks at the Top Level

Don't put Hooks inside loops, conditions, or nested functions. Instead, always use them at the top of your component. This helps React keep track of your Hooks in the right order, which is crucial for things like keeping your component's state consistent across re-renders.

Here's a no-go:

// Wrong
function MyComponent() {
  const items = [1, 2, 3];
  
  items.forEach(item => {
    const [count, setCount] = useState(0); // Oops, inside a loop
  });
}

And the right way:

// Correct
function MyComponent() {
  const [count, setCount] = useState(0); // Up top where it belongs

  const items = [1, 2, 3];
  
  items.forEach(item => {
    // It's cool to use state here
  });
}  
  • Only Call Hooks from React Functions

You can only use Hooks inside React function components or your own custom Hooks. They won't work in regular JavaScript functions.

This won't fly:

// Won't work
function someFunction() {
  const [count, setCount] = useState(0); // Trying this outside React
}

But this is perfect:

function useCounter() {
  const [count, setCount] = useState(0); // This is a custom Hook
  return { count, setCount };
}

function MyComponent() {
  const { count, setCount } = useCounter(); // Using the custom Hook here
}

Sticking to these rules makes sure your Hooks work as expected, keeping your components running smoothly. There's also a handy tool, eslint-plugin-react-hooks, that can help you stick to these rules without having to think too hard about it.

sbb-itb-bfaad5b

Common Pitfalls and Solutions

React Hooks are super handy, but there are a few common mistakes that can trip you up if you're not careful. Let's talk about these mistakes and how to fix them so you can avoid any headaches.

Stale Closures in useEffect

A common mistake with useEffect is ending up with a stale closure. This means you're accidentally using an old value of something that has changed, and it can lead to weird results.

For instance:

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

  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // Oops - using an old count value
    }, 1000);
    return () => clearInterval(id); 
  }, []);

  return (
    // Buttons to change count  
  );
}

In this example, the count inside setInterval doesn't update because it's stuck with the value from the first render.

How to fix it: Make sure your callback gets the latest value by including it in the useEffect dependency array:

useEffect(() => {

  const id = setInterval(() => {
    console.log(count); 
  }, 1000);

  return () => clearInterval(id);

}, [count]); 

Now, count will always be current.

Forgetting to Clean Up Subscriptions

Not cleaning up after yourself, like forgetting to unsubscribe from something, can lead to memory leaks.

Example problem:

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const channel = subscribeToData(data => 
      setData(data)
    );

    // Oops, forgot the cleanup
  }, []);

  // ...
}

Solution: Always include a cleanup function in your useEffect to prevent issues:

useEffect(() => {
  const channel = subscribeToData(data => 
    setData(data)
  );

  return () => {
    channel.unsubscribe();
  };
}, []);

Overusing useEffect

It's easy to want to put everything into useEffect, but doing so can cause unnecessary updates and slow things down.

What to do instead: Keep things that aren't side effects out of useEffect. For example, if you're just filtering items, do it outside of useEffect:

function MyComponent({ items }) {
  const [filteredItems, setFilteredItems] = useState(
    filterItems(items)  
  );

  // ...
}

This way, you keep your component running smoothly.

Understanding these common mistakes and how to fix them can make working with React Hooks a lot easier. Keeping an eye on closures, cleaning up properly, and using useEffect wisely will help you avoid problems.

Refactoring Class Components

Refactoring your existing React class components to use Hooks can make your code cleaner and more straightforward. Let's walk through how to change a class component into a function component using Hooks.

Before - Class Component

Here's a basic class component that shows a counter with buttons to add or subtract:

import React from "react";

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  decrement = () => {
    this.setState({ count: this.state.count - 1 }); 
  }

  render() {
    return (
      <div>
        <button onClick={this.decrement}>-</button>  
        <span>{this.state.count}</span>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

This component works well but it's a bit too complex for what it does. Using Hooks, we can make it simpler.

After - Function Component with Hooks

Now, let's convert that component to use the useState Hook:

import React, { useState } from "react";  

function Counter() {

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

  const increment = () => {
    setCount(count + 1);
  }
  
  const decrement = () => {
    setCount(count - 1);
  }

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>  
      <button onClick={increment}>+</button>
    </div>
  );
}

With useState, we can keep track of the count without needing a class. Our component is now a straightforward function that manages state with useState.

Here's why this is better:

  • It's simpler and has less code.
  • You don't have to worry about binding event handlers.
  • All the state stuff is done with just useState.
  • If you want to use the counter logic somewhere else, it's easier to make a custom hook.

In short, changing class components to use Hooks makes them easier to get, work with, and maintain. By cutting out the complex parts, your components are more focused on what they do best: showing stuff on the screen and handling user input.

Best Practices

When you're working with React Hooks, it's smart to keep things simple, direct, and ready to be used again. Here are some handy tips:

Keep Hooks Small and Focused

Do:

  • Create hooks for just one job, like keeping track of data or getting data from the internet
  • Give your hooks clear names that say what they do
  • Make sure the inside of your hooks is easy to follow

Don't:

  • Try to do too much in one hook
  • Make hooks that are big and complicated
  • Pick names for your hooks that don't make it clear what they do

Small, focused hooks are easier to check, fix, and use in other places.

Extract Shared Logic into Custom Hooks

If you see the same logic in more than one component, like getting data or handling state, pull it out into its own hook.

Benefits:

  • Keeps your components simple
  • Stops you from writing the same code over and over
  • Lets you use and combine logical parts easily

Here's how you might do it:

function useFetch(url) {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData);
  }, [url]);

  return data; 
}

function ComponentA() {
  const data = useFetch('/api/a');
  // ...
}

function ComponentB() {
  const data = useFetch('/api/b'); 
  // ...  
}

Now, both components can use the same logic for fetching data!

Follow React Hook Rules

Make sure to stick to the Rules of Hooks to keep away from bugs:

  • Use hooks at the top level, not inside loops or if statements
  • Only use hooks in React components or custom hooks

Adding eslint-plugin-react-hooks to your project can help you automatically spot problems.

By keeping your hooks straightforward, pulling out common logic, and following the rules, you'll create reliable, reusable hooks that make working with components much easier.

Conclusion

React Hooks are like special tools that make it easier to add features to your function components, without the need for complex class components. They help you manage state, deal with component updates, and share logic across your app in a simpler way.

Here's what you should remember:

  • Hooks help you deal with common issues like managing state and reusing logic without getting lost in complex class components.
  • Basic hooks like useState, useEffect, and useContext are your go-to for handling most of the work with state, updates, and context.
  • It's important to follow certain rules with hooks, like using them at the top of your components, to avoid running into problems.
  • Creating your own custom hooks can make your code cleaner by pulling out reusable logic into separate functions.
  • Keeping hooks small, focusing on one thing, and sticking to the rules can make your code better and your life easier.
  • Switching from class components to function components using hooks can make your code simpler and easier to understand.

In short, React Hooks offer a more straightforward way to work with React features like state and lifecycles in your function components. By choosing hooks over more complicated setups, your code becomes easier to handle, test, and share.

If this guide was helpful or if you have more questions about React Hooks, feel free to drop a comment. I'm always here to help with your React journey.

What is Hooks in React in simple words?

Hooks are special functions in React that let you use features like state (a way to remember things) and do things when your component loads or updates, all from within function components. This means you can do a lot more with simple functions without needing to write class components. Hooks help you reuse code and make your components smarter.

What is React with Hooks for beginners?

For beginners, here are a few things to know about React Hooks:

  • Hooks let function components use React features that were only available in class components before, like keeping track of data.
  • Common hooks such as useState, useEffect, and useContext are used for managing state, doing things after rendering, and accessing global data easily.
  • You should only use hooks at the top level of your components and not inside loops or conditions.
  • Custom hooks are a way to pull out component logic into reusable functions, making your components neater.
  • Learning hooks doesn't mean forgetting React basics. They're just another way to use React's features in your function components.

What are the 10 React Hooks?

Here are ten commonly used React hooks:

  • useState - Lets you track state changes.
  • useEffect - Runs code after render.
  • useContext - Accesses context data.
  • useReducer - Manages complex state logic.
  • useCallback - Caches functions.
  • useMemo - Caches values.
  • useRef - References DOM elements.
  • useImperativeHandle - Customizes ref values.
  • useLayoutEffect - Runs synchronously after DOM updates.
  • useDebugValue - Labels custom hooks in React DevTools.

What are the 15 Hooks in react JS?

Besides the built-in hooks, there are also custom hooks for various tasks:

  • useIdle - Detects user inactivity.
  • useInterval - Sets intervals.
  • useScroll - Handles scroll events.
  • useToggle - Toggles between states.
  • useTitle - Changes the document title.
  • usePrevious - Retrieves previous state or props.
  • useSetState - Combines state updates.
  • useCookie - Manages cookies.
  • useSpeech - Converts text to speech.
  • useGeolocation - Gets user location.
  • useMutationObserver - Observes changes in the DOM.
  • useWebSocket - Manages WebSocket connections.
  • useLocalStorage - Stores data in local storage.
  • useCss - Applies CSS dynamically.
  • useMediaQuery - Uses media queries.

Related posts

Why not level up your reading with

Stay up-to-date with the latest developer news every time you open a new tab.

Read more