Blog#94: Mastering React: 5 Essential Concepts for Every Developer

image.png

Hi, I'm Tuan, a Full-stack Web Developer from Tokyo 😊. Follow my blog to not miss out on useful and interesting articles in the future.

Introduction

React is a popular front-end library that is easy to learn and use. However, there are some concepts that developers should understand in order to write efficient and performant code. In this article, we will discuss some gotchas and concepts related to how state and effects work in React.

Deriving the State

Sometimes, we need to create a state variable that depends on another state variable. One way to do this is to use the useEffect hook and update the state variable that depends on the other one.

For example, let's say we have a select element that displays a list of user IDs. We want to track the selected user ID using a userId state variable. Here is how we might do this:

import { useState } from "react"  
  
const users = [  
  { id: "1", name: "User One" },  
  { id: "2", name: "User Two" },  
  { id: "3", name: "User Three" },  
]  
  
function Users () {  
  const [userId, setUserId] = useState("1")  
    
  return(  
    <select value={userId} onChange={e => setUserId(e.target.value)}>  
      <option value="1">User One</option>  
      <option value="2">User Two</option>  
      <option value="3">User Three</option>  
    </select>  
  );  
}

Now, let's say we want to show the selected user on the screen. We can create another state variable called selectedUser and update it whenever userId changes using the useEffect hook.

import { useState, useEffect } from "react"  
  
function Users () {  
  const [userId, setUserId] = useState("")  
  const [selectedUser, setSelectedUser] = useState(undefined)  
    
  useEffect(() => {  
    setSelectedUser(users.find(u => u.id === userId))  
  }, [userId])  
    
  return(  
    <>  
      <select value={userId} onChange={e => setUserId(e.target.value)}>  
        <option>Select a user</option>  
        <option value="1">User One</option>  
        <option value="2">User Two</option>  
        <option value="3">User Three</option>  
      </select>  
      {selectedUser && <p>The selected user is: {selectedUser.name}</p>}  
    </>  
  );  
}

This approach works, but it is not the most efficient way to update selectedUser. The problem with this method is that the component first renders when userId changes, and then the useEffect hook is triggered after the render because userId is passed in its dependency array. This means that the component renders twice just to update selectedUser, first when userId changes and second when the useEffect hook updates selectedUser.

A better approach is to simply derive the value of selectedUser directly from userId.

function Users () {  
  const [userId, setUserId] = useState("")  
  const selectedUser = users.find(u => u.id === userId)  
    
  return(  
    <>  
      <select value={userId} onChange={e => setUserId(e.target.value)}>  
        <option>Select a user</option>  
        <option value="1">User One</option>  
        <option value="2">User Two</option>  
        <option value="3">User Three</option>  
      </select>  
      {selectedUser && <p>The selected user is: {selectedUser.name}</p>}  
    </>  
  );  
}

This works because selectedUser is derived directly from userId and is not tracked as a separate state variable. This means that the component will only re-render when userId changes, rather than re-rendering twice as in the previous example.

Set State inside Event Handlers

Sometimes, we need to update the state inside event handlers, such as a click event or a form submission event. In these cases, it's important to make sure that the state update is performed asynchronously, so that the component has a chance to re-render before the event handler finishes executing.

Here is an example of how to correctly set the state inside an event handler:

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

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>The count is: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

In this example, we have a Counter component that displays a count and has a button that increments the count when clicked. The handleClick event handler updates the count state variable using the setCount function.

It's important to note that the setCount function is asynchronous, which means that the state update may not be immediately reflected in the component. Instead, the component will re-render after the event handler finishes executing.

If we need to perform an operation that depends on the current value of the state, we can pass a function to the setCount function, like this:

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

  const handleClick = () => {
    setCount((currentCount) => currentCount + 1);
  };

  return (
    <div>
      <p>The count is: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

In this example, the setCount function is called with a function that takes the current value of count as an argument and returns the new value. This ensures that the state update is performed correctly, even if the component has not yet had a chance to re-render.

Cleanup Functions

Sometimes, we need to perform some cleanup actions when a component is unmounted or when a certain condition is no longer met. In these cases, we can use a cleanup function with the useEffect hook.

A cleanup function is a function that is called when a component is unmounted or when the dependencies of an useEffect hook change. It is a good place to perform any necessary cleanup, such as canceling network requests or removing event listeners.

Here is an example of how to use a cleanup function with the useEffect hook:

function Users() {
  const [userId, setUserId] = useState(1);
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`/users/${userId}`);
      const data = await response.json();
      setUser(data);
    };

    fetchUser();

    return () => {
      // Cleanup function
      console.log("Cleaning up...");
    };
  }, [userId]);

  return (
    <div>
      {user ? (
        <p>The user is: {user.name}</p>
      ) : (
        <p>Loading...</p>
      )}
      <button onClick={() => setUserId(2)}>Load User 2</button>
    </div>
  );
}

In this example, we have a Users component that fetches a user from a backend API based on the userId state variable. The useEffect hook is used to trigger the fetch when userId changes.

The useEffect hook also has a cleanup function that is called when the component is unmounted or when the userId state variable changes. In this case, the cleanup function simply logs a message to the console.

It's important to note that the cleanup function is only called when the dependencies of the useEffect hook change or when the component is unmounted. This means that if the userId state variable changes but the useEffect hook's dependencies do not, the cleanup function will not be called.

Updating the State when a Prop changes

Sometimes, we need to update the state of a component based on a prop that is passed to the component. In these cases, we can use the useEffect hook to detect when the prop changes and update the state accordingly.

Here is an example of how to update the state when a prop changes:

function User({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`/users/${userId}`);
      const data = await response.json();
      setUser(data);
    };

    fetchUser();
  }, [userId]);

  return user ? <p>The user is: {user.name}</p> : <p>Loading...</p>;
}

In this example, we have a User component that fetches a user from a backend API based on the userId prop. The useEffect hook is used to trigger the fetch when the userId prop changes.

It's important to note that the useEffect hook's dependencies array should include any props or state variables that the effect depends on. In this case, the effect depends on the userId prop, so it is included in the dependencies array.

If the useEffect hook's dependencies are not specified correctly, the effect may be triggered unnecessarily, which can lead to performance issues.

State preservation in the same position

Sometimes, we need to preserve the state of a component when it is re-rendered in the same position on the page. In these cases, we can use the useState hook with the useMemo hook to preserve the state.

The useMemo hook is a performance optimization that allows us to memoize values so that they are only recomputed when the dependencies of the hook change. This can be useful for preserving the state of a component when it is re-rendered in the same position.

Here is an example of how to use the useMemo hook to preserve the state of a component:

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  const increment = useMemo(() => {
    return () => setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>The count is: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

In this example, we have a Counter component that displays a count and has a button that increments the count when clicked. The increment function is created using the useMemo hook and is only recomputed when the count state variable changes. This means that the increment function will always have the correct value of count, even if the Counter component is re-rendered in the same position on the page.

It's important to note that the useMemo hook's dependencies array should include any props or state variables that the memoized value depends on. In this case, the increment function depends on the count state variable, so it is included in the dependencies array.

If the useMemo hook's dependencies are not specified correctly, the memoized value may be recomputed unnecessarily, which can lead to performance issues.

Conclusion

We discussed several concepts related to state and effects in React. We covered topics such as deriving the state, setting the state inside event handlers, using cleanup functions with the useEffect hook, updating the state when a prop changes, and preserving the state of a component when it is re-rendered in the same position on the page.

Understanding these concepts is important for writing efficient and performant code in React, as well as for understanding how state and effects work in the React framework.

As always, I hope you enjoyed this article and learned something new. Thank you and see you in the next articles!

If you liked this article, please give me a like and subscribe to support me. Thank you. 😊

NGUYỄN ANH TUẤN

Xin chào, mình là Tuấn, một kỹ sư phần mềm đang làm việc tại Tokyo. Đây là blog cá nhân nơi mình chia sẻ kiến thức và kinh nghiệm trong quá trình phát triển bản thân. Hy vọng blog sẽ là nguồn cảm hứng và động lực cho các bạn. Hãy cùng mình học hỏi và trưởng thành mỗi ngày nhé!

Đăng nhận xét

Mới hơn Cũ hơn