Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a Pure Wrapper for a React Component

Tags:

reactjs

I'm using the why-did-you-render package to debug some performance issues in my react app. I'm getting the following console message regarding why my XDrag component is re-rendering:

XDrag
defaultNotifier.js:40 {XDrag: ƒ} "Re-rendered because of props changes:"
defaultNotifier.js:45 props.children
defaultNotifier.js:49 different React elements (remember that the <jsx/> syntax always produces a *NEW* immutable React element so a component that receives <jsx/> as props always re-renders). (more info here)
defaultNotifier.js:52 {prev children: {…}} "!==" {next children: {…}}

In the article linked to in this message, they suggest using a "pure wrapper" component around our original component to avoid this rerender:

class PureFatherWrapper extends React.PureComponent{
  render(){
    return (
      <PureFather>
        <SomeChild/>
      </PureFather>
    )
  }
}
  1. What exactly do they mean by a pure wrapper/ pure component? How is a pure component different from other kinds of components?
  2. Why doe this pure wrapper avoid a re-render?
  3. How could I implement a Pure Wrapper for my XDrag Component?
import React, { FC, ReactNode } from "react";
import { Draggable, DraggableProps } from "react-beautiful-dnd";

interface IXDrag extends Omit<DraggableProps, "children"> {
  className?: string;
  children: ReactNode;
  dragAll?: boolean;
}

const XDrag: FC<IXDrag> = ({ className, children, dragAll, ...props }) => {
  console.log(React.isValidElement(children));
  if (!React.isValidElement(children)) return <div />;
  return (
    <Draggable {...props}>
      {(provided, snapshot) => {
        const dragHandleProps = dragAll ? provided.dragHandleProps : {};
        return (
          <tr
            className={className}
            ref={provided.innerRef}
            {...provided.draggableProps}
            {...dragHandleProps}
          >
            {React.cloneElement(children, { provided })}
          </tr>
        );
      }}
    </Draggable>
  );
};

Using XDrag:

import React from "react";
import { useStoreState } from "../../hooks";
import XDrag from "../XDrag";
import TableHeader from "./TableHeader";

const ContentTable = () => {
  const cardItems = useStoreState(
    (state) => state.appModel.activeCards
  );

  return (
        <table>
          <tbody>
            <tr>
              <TableHeader
                title={"URL"}
              />
            </tr>
            {cardItems.map((card, i) => {
              return (
                <XDrag
                  draggableId={card.sourceId}
                  index={i}
                  key={i.toString()}
                  isDragDisabled={card.isActive}
                >
                    <td>{card.src}</td>
                </XDrag>
              );
            })}
          </tbody>
        </table>
  );
};

Some Context: I'm using the react beautiful dnd library to create some draggable rows in a table. Each XDrag provides a <tr> for all the <td>s.

like image 484
ANimator120 Avatar asked Oct 18 '25 10:10

ANimator120


1 Answers

I will try to answer each question and hopefully give you some extra information to help you make a decision.

1. What exactly do they mean by a pure wrapper/ pure component? How is a pure component different from other kinds of components?

In simple words, a pure component (or pure function) is a component (or function) that will always return the same output for the same input (by the way, this is not exclusive to React).

Example: function add(x, y){return x + y} is a pure function because add(1, 2) will always return 3, no matter how many times you run it.

PureComponents have the same idea. You can read more about PureComponents in the official docs.

2. Why does this pure wrapper avoid a re-render

In the article that you linked, this is the key part to answer this question:

Always remember: the <jsx prop='a'/> syntax is just a sugar for: React.createElement('jsx', {prop: 'a'}). This means whenever it’s father re-renders, jsx tries to update itself with a new props object.

A PureComponent would know that the prop: a didn't change, so it would not rerender. This happens because of the Shallow Compare utility.

3. How could I implement a Pure Wrapper for my XDrag Component?

You could either use the suggestion from the article (create a PureWrapper component and use it to wrap your <XDrag>) or use the useCallback hook (or useMemo). Something like this:

const ContentTable = () => {
  const cardItems = useStoreState((state) => state.appModel.activeCards);

  const renderCardItems = useCallback(() => {
    return cardItems.map((card, i) => {
      return (
        <XDrag draggableId={card.sourceId} index={i} key={i.toString()} isDragDisabled={card.isActive} >
          <td>{card.src}</td>
        </XDrag>
      );
    })
  }, [cardItems])
    
  return (
    <table>
      <tbody>
        <tr>
          <TableHeader title={"URL"} />
        </tr>
        {renderCardItems()}
      </tbody>
    </table>
  );
};

Here, useCallback has a dependency array (like useEffect), which means that this function only changes if cardItems changes. In other words, renderCardItems() will always return the same result as long as cardItems is the same.


Note:

Hopefully this helped to clarify some of your questions.

That said, the real question is: "why do you need to optimize this rerender?"

We often worry about optimization when we don't need to. React is already pretty good with that, and browsers today have much more power. Unless you have a specific requirement or a really bad performance, you should not add complexity to your code just to avoid some rerenders.

In fact, "avoid early optmizations" is one clean code principle.

Here is a really good article with more information on that: https://kentcdodds.com/blog/usememo-and-usecallback

like image 84
Bruno Monteiro Avatar answered Oct 21 '25 23:10

Bruno Monteiro