DEV Community

abosaiftaha
abosaiftaha

Posted on

React Hooks Made Easy: A Step-by-Step Tutorial (Part 1)

React, the widely adopted JavaScript library developed by Facebook, has revolutionized web development since its release in 2013. Known for its efficient rendering, component-based architecture, declarative syntax, and strong community support, React has become the go-to choice for building modern, scalable web applications. With a virtual DOM, reusability, modularity, and a vibrant ecosystem, React offers developers a powerful toolkit to create efficient, interactive, and maintainable user interfaces. Its popularity continues to soar, making it an essential skill for developers in today's web development landscape.

React introduced hooks in version 16.8, bringing a significant shift in how developers write components. These hooks offer a simpler and more effective approach to managing state, handling side effects, and sharing data between components. In this article, we explored seven of the most commonly used React hooks: useState, useEffect, useContext, useRef, useMemo, useReducer, and useCallback. By understanding these hooks, you now have a comprehensive understanding of each hook's usage and benefits, enabling you to utilize them confidently in your projects.

In Part 1 of this article, we will cover the useState, useEffect, and useRef hooks. useState allows us to manage state within functional components, useEffect enables us to handle side effects and manage the component lifecycle, and useRef provides a way to access and modify DOM elements and store values between renders.

In Part 2, we will dive into the useMemo and useCallback hooks, which optimize performance by memoizing values and functions, respectively. We will explore their use cases and understand how they can improve the efficiency of your React applications.

Finally, in Part 3, we will cover the useContext and useReducer hooks, and provide an overview of Redux, a popular state management library in the React ecosystem. These hooks and Redux will empower you to manage complex state and share data across your application with ease.

UseState

Managing State in Functional Components
Gone are the days of converting functional components into class components just to manage state. With the useState hook, we can easily add state to functional components. By calling useState with an initial value, React returns an array with two elements: the current state value and a function to update it. This allows us to maintain state within a functional component without sacrificing its simplicity and readability.

Let's see an example that demonstrates the usage of useState:

import React, { useState } from 'react';

function Counter() {
  // useState hook to manage count state
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1); // Update the count state
  };

  const decrement = () => {
    setCount(count - 1); // Update the count state
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we import the useState hook from 'react' and use it to manage the count state. We initialize count to 0 using useState, and it returns the current value of count (extracted from the returned array) and the setCount function.

We define two functions, increment and decrement, which are triggered by the respective buttons. Inside these functions, we use setCount to update the value of count by adding or subtracting 1.

In the JSX code, we display the current value of count using the curly braces notation {count}. Whenever the user clicks the "Increment" or "Decrement" button, the state is updated, triggering a re-render of the component and displaying the updated value.

By using the useState hook, we can easily manage and update state within functional components, enabling us to create dynamic and interactive user interfaces.

useEffect

Handling Side Effects
The useEffect hook enables us to perform side effects, such as fetching data from an API, subscribing to events, or manipulating the DOM, within our components. It accepts a callback function and executes it after the component renders. This powerful hook also provides a way to clean up any resources or subscriptions when the component unmounts or before re-rendering.

Let's see an example that demonstrates the usage of useEffect:

import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    // Runs after every render
    console.log('Effect: Runs after every render');

    // Clean-up function
    return () => {
      console.log('Clean-up: Runs before the next render');
    };
  });

  useEffect(() => {
    // Runs only once after the initial render
    console.log('Effect: Runs only once after the initial render');
  }, []);

  useEffect(() => {
    // Runs whenever 'count' changes
    console.log('Effect: Runs whenever "count" changes');
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we have an ExampleComponent that uses the useState and useEffect hooks from React. Inside the component, we initialize a state variable count and provide a button to increment its value.

We use three separate useEffect hooks to demonstrate different states:

  1. The first useEffect hook does not have a dependency array specified. It runs after every render of the component. In this example, it logs a message to the console indicating that it runs after every render. It also returns a clean-up function, which runs before the next render (effectively cleaning up any resources or subscriptions).

  2. The second useEffect hook has an empty dependency array []. It runs only once after the initial render. In this example, it logs a message to the console indicating that it runs only once after the initial render.

  3. The third useEffect hook has [count] as its dependency array. It runs whenever the count value changes. In this example, it logs a message to the console indicating that it runs whenever the count value changes.

By observing the console logs in your browser's developer tools, you can see the different states of the useEffect hook and how they correspond to different scenarios during the component lifecycle.

Understanding these different states of useEffect allows you to perform actions based on specific conditions or dependencies, ensuring your components behave as expected and efficiently manage side effects.

Another common use case of the useEffect hooks is fetching data from API's:

import React, { useState, useEffect } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Simulating an API call to fetch user data
    fetch('https://api.example.com/user')
      .then(response => response.json())
      .then(data => setUser(data));
  }, []);

  return (
    <div>
      {user ? (
        <div>
          <p>Name: {user.name}</p>
          <p>Email: {user.email}</p>
        </div>
      ) : (
        <p>Loading user profile...</p>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we import the useState and useEffect hooks from 'react'. Inside the UserProfile component, we initialize a state variable user using useState and set its initial value to null.

We use the useEffect hook to fetch user data from an API. The effect is triggered after the initial render (due to the empty dependency array []). Inside the effect's callback function, we simulate the API call using the fetch function. Upon receiving the response, we extract the data and update the user state using the setUser function.

In the JSX code, we conditionally render the user's profile information if the user state is not null. Otherwise, we display a loading message.

The useEffect hook enables us to incorporate asynchronous operations, such as data fetching, into our functional components effortlessly. It ensures that the effect runs at the appropriate times during the component lifecycle, making it an essential tool for managing side effects.

useRef

Accessing and Modifying DOM Elements
Sometimes, we need to access or modify DOM elements directly within our components. The useRef hook allows us to create a mutable ref object, which persists across component renders. We can use the ref object to reference DOM elements, store values between renders, or trigger imperative actions. It provides a convenient way to interact with the DOM without breaking the declarative nature of React. useRef can be used in various situations, such as accessing input values, focusing elements, or preserving values between renders.

Example 1: Accessing Input Values

import React, { useRef } from 'react';

function InputForm() {
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Input value:', inputRef.current.value);
    inputRef.current.value = '';
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={inputRef} />
      <button type="submit">Submit</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we create a ref using useRef and assign it to the inputRef variable. We attach this ref to the input element using the ref attribute. When the form is submitted, the handleSubmit function is called, accessing the value of the input field via inputRef.current.value. We can also update the input value by assigning a new value to inputRef.current.value.

Example 2: Focusing an Input Field

import React, { useRef, useEffect } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus Input</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we use useRef to create a ref named inputRef and attach it to an input element. Inside the useEffect hook, we specify an empty dependency array [] to ensure it runs only once after the initial render. Within the effect, we call inputRef.current.focus() to focus the input field when the component mounts.

Additionally, we provide a button that, when clicked, also calls inputRef.current.focus() to focus the input field. This demonstrates how useRef can be used to access and interact with DOM elements imperatively.

Example 3: Preserving Values between Renders

import React, { useRef, useState } from 'react';

function Counter() {
  const counterRef = useRef(0);
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
    counterRef.current += 1;
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Counter Ref: {counterRef.current}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we use useRef to create a ref called counterRef and initialize it with a value of 0. We also have a state variable count managed by useState. When the "Increment" button is clicked, the increment function is called, updating the count state and also incrementing the value of counterRef.current.

By storing a value in a ref, we can preserve it between renders without causing a re-render. This can be useful in scenarios where you want to store a value or reference that should persist across component re-renders.

These examples demonstrate some of the use cases for useRef. Remember that useRef allows us to create a mutable reference that persists between renders, giving us direct access.

Why would i use useRef instead of useState for these cases?
Good question! While both useRef and useState can be used to store values, they have different purposes and use cases:

  1. useRef:
  • Use useRef when you need to store a mutable value that persists across renders, without causing a re-render.
  • useRef is primarily used to access and manipulate DOM elements directly or to store values that need to be preserved between renders.
  • The value stored in a ref can be updated without triggering a re-render of the component.
  1. useState:
  • Use useState when you need to store a value that triggers a re-render when it changes.
  • useState is used to manage state within functional components and triggers a re-render whenever the state value is updated.
  • The value stored in useState is typically used to control the component's behavior, such as rendering different content, updating UI, or triggering side effects.

In simpler terms, useRef is suitable when you want to maintain a value across renders without causing a re-render. It's commonly used for accessing DOM elements, storing previous values, or managing mutable data.

On the other hand, useState is used when you want to track a value that affects the component's rendering and behavior. It triggers a re-render when the state value changes and is commonly used for managing UI-related states and application logic.

Conclusion

React hooks have transformed the way we approach web development, providing a more intuitive and functional approach to building components. With their ability to manage state, handle side effects, simplify communication, and optimize performance, hooks have become an integral part of modern React applications. By understanding and utilizing these hooks effectively, developers can unlock the full potential of React and create exceptional user experiences.

Stay tuned for more articles on React and its evolving ecosystem. Happy coding!

Top comments (0)