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

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.

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.

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