+6

The Ugliest Pattern in React: When and Why to Use it

React is a popular JavaScript library for building user interfaces, and it's known for its efficiency and simplicity. However, there is one pattern in React that stands out as particularly ugly: updating state in response to rendering. In this article, we'll take a closer look at this pattern, when you might need to use it, and some alternatives that may be more suitable in certain situations.

What is the Ugliest Pattern in React?

In React, you typically update state in event handlers. However, there are rare cases where you might want to adjust state in response to rendering - for example, if you want to change a state variable when a prop changes. Here's an example of this pattern in action:

function CountLabel({ count }) {
  const [prevCount, setPrevCount] = useState(count);
  const [trend, setTrend] = useState(null);
  if (prevCount !== count) {
    setPrevCount(count);
    setTrend(count > prevCount ? 'increasing' : 'decreasing');
  }
  return (
    <>
      <h1>{count}</h1>
      {trend && <p>The count is {trend}</p>}
    </>
  );
}

As you can see, this code sets state in the render function, which goes against the principle that the render function should be pure. This can make the code difficult to understand and is generally best avoided.

When to Use the Ugliest Pattern in React

So, when should you use this pattern? According to the React documentation, there are a few cases where it might be necessary:

If the value you need can be computed entirely from the current props or other state, you can remove the redundant state altogether. If you want to reset the entire component tree's state, you can pass a different key to your component. If you can, update all the relevant state in event handlers. If none of these conditions apply and you still need to update state in response to rendering, then you may have to use the ugliest pattern in React.

Examples

Now that we've covered the basics of this pattern, let's look at a few examples of when you might need to use it.

Example 1: Updating a Progress Bar

Imagine you have a progress bar component that displays the percentage of a task that has been completed. The percentage is passed to the component as a prop, and the component should update the width of the progress bar to reflect the percentage. Here's how you might use the ugliest pattern in React to achieve this:

function ProgressBar({ percentage }) {
  const [prevPercentage, setPrevPercentage] = useState(percentage);
  const [width, setWidth] = useState(0);
  if (prevPercentage !== percentage) {
    setPrevPercentage(percentage);
    setWidth(percentage);
  }
  return (
    <div style={{ width: `${width}%` }}>
      {percentage}% complete
    </div>
  );
}

Example 2: Displaying a Loading Indicator

Suppose you have a component that fetches data from an API and displays it to the user. While the data is being fetched, you want to display a loading indicator to let the user know that something is happening. Here's how you might use the ugliest pattern in React to achieve this:

function DataTable() {
  const [prevLoading, setPrevLoading] = useState(true);
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState([]);
  if (prevLoading !== loading) {
    setPrevLoading(loading);
  }
  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      const response = await fetch('https://my-api.com/data');
      const data = await response.json();
      setData(data);
      setLoading(false);
    }
    fetchData();
  }, []);
  return (
    <div>
      {loading && <p>Loading...</p>}
      {data.length > 0 && (
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Age</th>
              <th>Location</th>
            </tr>
          </thead>
          <tbody>
            {data.map(item => (
              <tr key={item.id}>
                <td>{item.name}</td>
                <td>{item.age}</td>
                <td>{item.location}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

In this example, we use the useEffect hook to fetch data from an API when the component mounts. While the data is being fetched, we set the loading state to true, which causes the loading indicator to be displayed. When the data has been successfully fetched, we set the loading state to false, which causes the loading indicator to be hidden and the data to be displayed in a table.

It's worth noting that the ugliest pattern in React is not the only way to display a loading indicator in this situation. You could also use the useEffect hook to update the loading state when the data is being fetched, or you could use the shouldComponentUpdate lifecycle method to prevent unnecessary re-renders. Ultimately, the best approach will depend on your specific needs and the requirements of your application.

Example 3: Displaying a Notification

Imagine you have a notification component that displays a message to the user. The message is passed to the component as a prop, and the component should update the message when the prop changes. Here's how you might use the ugliest pattern in React to achieve this:

function Notification({ message }) {
  const [prevMessage, setPrevMessage] = useState(message);
  const [displayMessage, setDisplayMessage] = useState(null);
  if (prevMessage !== message) {
    setPrevMessage(message);
    setDisplayMessage(message);
  }
  return (
    <div>
      {displayMessage && <p>{displayMessage}</p>}
    </div>
  );
}

Example 4: Updating a Form Field

Suppose you have a form component that allows the user to enter their email address. The component should update the email address when the user types in the input field. Here's how you might use the ugliest pattern in React to achieve this:

function EmailForm() {
  const [prevEmail, setPrevEmail] = useState('');
  const [email, setEmail] = useState('');
  if (prevEmail !== email) {
    setPrevEmail(email);
  }
  return (
    <form>
      <label>
        Email:
        <input
          type="email"
          value={email}
          onChange={event => setEmail(event.target.value)}
        />
      </label>
    </form>
  );
}

Example 5: Updating a Navbar

Imagine you have a navbar component that displays the current page the user is on. The current page is passed to the component as a prop, and the component should highlight the corresponding nav item when the prop changes. Here's how you might use the ugliest pattern in React to achieve this:

function Navbar({ currentPage }) {
  const [prevPage, setPrevPage] = useState(currentPage);
  const [activeItem, setActiveItem] = useState(null);
  if (prevPage !== currentPage) {
    setPrevPage(currentPage);
    setActiveItem(currentPage);
  }
  return (
    <nav>
      <ul>
        <li className={activeItem === 'home' ? 'active' : ''}>Home</li>
        <li className={activeItem === 'about' ? 'active' : ''}>About</li>
        <li className={activeItem === 'contact' ? 'active' : ''}>Contact</li>
      </ul>
    </nav>
  );
}

Alternatives to the Ugliest Pattern in React

While the ugliest pattern in React can be useful in certain situations, it's generally best to avoid it if possible. Here are a few alternatives you might consider:

  1. Use the useEffect hook to update state in response to prop changes. This is the recommended approach, but it can cause unnecessary re-renders if not used carefully.
  2. Use the shouldComponentUpdate lifecycle method to prevent unnecessary re-renders. This is a good option if you need more control over when the component updates, but it can make your code less readable.
  3. Use a memoization library like reselect to compute derived data from props and state. This can help to avoid unnecessary re-renders and make your code more efficient.

Conclusion

The ugliest pattern in React is a pattern that should be used with caution. While it can be useful in certain situations, it's generally best to avoid it if possible and use alternatives like useEffect or memoization instead. Remember to carefully consider your options and choose the approach that makes the most sense for your specific use case.

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí