deco-cx / community

Deco community material
MIT License
1 stars 0 forks source link

## Leveraging Native Web APIs to Reduce Bundle Size and Improve Performance #9

Closed tlgimenes closed 3 months ago

tlgimenes commented 4 months ago

Leveraging Native Web APIs to Reduce Bundle Size and Improve Performance

In modern web development, libraries like React have become the norm for building dynamic and interactive user interfaces. However, relying heavily on such libraries can sometimes lead to larger bundle sizes and reduced performance. By leveraging native web APIs, we can accomplish common design patterns more efficiently, enhancing performance and overall web compatibility. In this article, we will explore how to use these APIs to reduce our dependence on React hooks like useState.

Case Study: The Hidden <input type="checkbox" /> Hack

One often overlooked technique in native web development is the hidden <input type="checkbox" /> hack. This trick, which has been around for a while, offers a way to control UI state without relying on JavaScript, reducing bundle size and potentially improving performance.

The React Approach

Let’s start with a typical example in React:

hello

import { useState } from "preact/hooks";

export default function Section() {
  const [display, setDisplay] = useState(false);

  return (
    <div class="p-10 grid place-items-center gap-2">
      <button onClick={() => setDisplay(!display)} class="btn">Click Me</button>
      {display && <div>Hello!</div>}
    </div>
  );
}

In this example, the useState hook and the onClick handler are used to toggle the visibility of a piece of UI. While this approach is effective, it involves additional JavaScript, which can contribute to a larger bundle size.

The Native Web API Approach

Now, let’s refactor this example to use only native web APIs:

export default function Section() {
  return (
    <div class="p-10 grid place-items-center gap-2">
      <input id="toggle" type="checkbox" class="peer hidden" />
      <label for="toggle" class="btn">Click Me</label>
      <div class="hidden peer-checked:inline">Hello!</div>
    </div>
  );
}

Explanation

So, what’s happening here? We’ve replaced the useState hook with a hidden <input type="checkbox" /> element. Here’s a breakdown of the changes:

  1. Hidden Checkbox: We introduce an <input type="checkbox" /> with an id and the hidden class. This checkbox will control the state.
  2. Label for Toggle: The <label for="toggle"> is linked to the checkbox using the for attribute, which toggles the checkbox state when clicked.
  3. Peer Class: Using Tailwind CSS's peer class, we can style elements based on the state of the checkbox. The peer-checked:inline class makes the <div> appear when the checkbox is checked.

This final result from both the React and Native Web API are indistinguishable to the final user, only changing the performance/compatibility characteristics of the application. Also, this trick can be applied in Drawers, Modals and any other kind of boolean UI. Bellow are some examples of this usage:

More Examples
  1. Drawer drawer

  2. Modal modal

  3. Coupon code coupon

Case Study: The Forgotten <input type="radio" /> Hack

Another powerful yet often overlooked HTML element is the <input type="radio" />. This can be especially useful for creating SKU selectors or similar UI components. Let’s examine how we can refactor a React component to use native radio inputs.

radio

The React Approach

First, let’s look at a typical React component for a SKU selector:

import { useState } from "preact/hooks";

const options = ["1", "2", "3"];

export default function Section() {
  const [selected, setSelected] = useState(0);

  return (
    <ul class="p-10 flex items-center justify-center gap-2">
      {options.map((o, index) => (
        <li key={index}>
          <button
            onClick={() => setSelected(index)}
            class={`btn btn-circle ${index === selected ? "btn-primary" : ""}`}
          >
            {o}
          </button>
        </li>
      ))}
    </ul>
  );
}

In this example, the useState hook and onClick handlers are used to manage and update the selected state. This approach, while effective, involves additional JavaScript.

The Native Web API Approach

Now, let’s refactor this example to use <input type="radio" /> elements:

const options = ["1", "2", "3"];

export default function Section() {
  return (
    <ul class="p-10 flex items-center justify-center gap-2">
      {options.map((o, index) => (
        <li key={index}>
          <input
            id={o}
            name="options"
            type="radio"
            class="peer hidden"
            defaultChecked={index === 0}
          />
          <label for={o} class="btn btn-circle peer-checked:btn-primary">
            {o}
          </label>
        </li>
      ))}
    </ul>
  );
}

Explanation

Here’s a breakdown of how we refactored the React component to use native web APIs:

  1. Radio Inputs: We introduce <input type="radio" /> elements, each with a unique id and the same name attribute. The name property ensures that only one radio button can be selected at a time within the group.
  2. Hidden Class: The hidden class hides the radio buttons from view.
  3. Label for Toggle: The <label for={o}> is linked to the corresponding radio input using the for attribute, making the label clickable to change the radio button’s state.
  4. Default Checked: We use the defaultChecked attribute to set the initial checked radio button.
  5. Peer Class: With Tailwind CSS’s peer class, we can style the labels based on the state of the radio inputs. The peer-checked:btn-primary class applies the btn-primary style to the selected label.

Benefits and Drawbacks

Using the techniques presented in this article can lead to significant performance improvements for your website. By reducing the amount of JavaScript required, you decrease parse and compile times, thereby improving Total Blocking Time (TBT). Additionally, adding animations and transitions becomes simpler, as these can be managed directly within the DOM, eliminating the need for third-party libraries often required in React.

Another advantage of these native web API hacks is enhanced browser compatibility. Since these techniques are supported by all major browsers, your website will function consistently across different platforms, providing a seamless experience for all users. Furthermore, reducing dependency on external libraries can simplify your project and reduce potential security vulnerabilities, while better performance on low-end devices ensures a broader audience can have a good user experience.

However, there are some tradeoffs to consider. One drawback is the potential increase in initial DOM size, as all states must be represented in the DOM. This can lead to a higher First Contentful Paint (FCP), as the browser has to load more content initially. Additionally, while native web APIs are great for many use cases, they might not be suitable for highly interactive applications where complex state management and frequent updates are necessary. In such cases, frameworks like React offer more powerful and flexible solutions. SEO considerations also come into play, as you need to ensure that content visibility managed through the DOM is still accessible and indexable by search engines. Lastly, as your application scales, maintaining complex UI interactions purely through native web APIs and CSS might become challenging, making the abstractions provided by frameworks beneficial for managing complexity.

Conclusion

Using native web APIs like <input type="checkbox" /> and <input type="radio" /> can help reduce the dependency on JavaScript, resulting in smaller bundle sizes and potentially better performance. While this approach has its tradeoffs, it offers a streamlined and efficient way to handle common UI patterns.

Stay tuned for more insights into leveraging native web APIs for a more efficient web development process!

tlgimenes commented 3 months ago

https://deco.cx/en/blog/from-react-to-native