React.js with Factory Pattern ? Building Complex UI With Ease

Shadid Haque - Nov 17 '19 - - Dev Community

Objective:

We will discuss

  • What is factory pattern and how is it used to build complex, scalable UI ?
  • Increase performance of your application with React memoization and factory pattern

Imagine we are building a dashboard. When our user logs in we get a user object back from our back-end. Based on that object we will determine what our user sees in the dashboard. Let’s say user_a is an object that is returned from our server

user_a = {
   name: "jhon doe",
   items: [
     {
       title: "Card 1",
       details: {
         // ...more info
       },
       type: "A"
     },
     {
       title: "Card 2",
       details: {
         // ...more info
       },
       type: "B"
     },
     {
       title: "Card 3",
       details: {
         // ...more info
       },
       type: "C"
     },
     {
       title: "Card 4",
       details: {
         // ...more info
       },
       type: "D"
     }
   ]
 }
Enter fullscreen mode Exit fullscreen mode

Based on the type of object we will render a card component. Let’s imagine that this is what our users would see:

N|Solid

Now that looks pretty straight forward. We have 4 types of card components so we can write our components as follows and render them in our App component (Container Component) with a simple switch statement

function A() {
 return(
   <div>Type A Component</div>
 )
}

function B() {
 return(
   <div>Type B Component</div>
 )
}

function C() {
 ...
}

function D() {
 ...
}

Enter fullscreen mode Exit fullscreen mode

App.jsx

function App() {
  return (
      <div>
        {cards.map(card => {
          switch (card.type) {
            case "A":
              return <A />;
            case 'B':
              return <B />;
            case 'C':
              return <C />;
            case 'D':
              return <D />;
            default:
              return null;
          }
        })}
     </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

However, over time as our application becomes popular we were told to add 10 more different types of card components. So now we have cards A,B,C,D,E,F,G,H,I,J,K … and so on. Now of course we can choose to keep adding if statements or a switch statement. However, soon it will start to look messy.

Also we will be violating the Single Responsibility Principle. App component should not be concerned with what to render based on payload. We need a layer of abstraction to keep our code clean.

aside: If you would like to learn more about SOLID patterns in React Check out my other article here.

Alright, so here's the solution. Instead of creating many if statements we create a function that will dynamically create our components based on the payload. This function is called the factory function.

function Factory(props) {
  switch (props.component.type) {
    case "A":
      return <A />;
    case "B":
      return <B />;
    case "C":
      return <C />;
    default:
      return <div>Reload...</div>;
  }
}
Enter fullscreen mode Exit fullscreen mode

And our App now looks like this.

return (
   <div>
     {cards.map(card => (
       <Factory component={card} />
     ))}
   </div>
 );
Enter fullscreen mode Exit fullscreen mode

In brief, using a factory function to dynamically create components is factory pattern.

But, You’re not convinced !!

Alright, so using a factory function separates our concerns. For complex large applications it helps us maintain the code, it's nice to have but you are still not convinced. Why should I even bother writing that extra function? I need more reasons.

Well, that brings us to our next point, performance.

We can effectively use the factory method to memoize our components and prevent unnecessary re-rendering.

Cache with React.memo() hook.

memo() hook from react is a mechanism that helps us cache our components. When the props passed into a component are the same as previous then we return the cached version of that component. This prevents the re-rendering of that component and increases our app performance.

If you are new to hooks or never used memo() before I highly suggest giving the official documentation a read.

Right now, our App Component is re-rendering all of our card components when some props are being changed.

Let’s revisit the example from above to elaborate

function App() {
  const cards = [
    {
      name: "shadid",
      type: "A"
    },
    {
      name: "shadid",
      type: "C"
    },
    {
      name: "shadid",
      type: "B"
    },
    {
      name: "shadid",
      type: "A"
    }
    .... more of these card objects
  ];
  const [count, setCount] = React.useState(0);
  const doStuff = () => {
    setCount(count + 1);
    console.log(" Click ---> ");
  };
  return (
    <div>
      {cards.map(card => (
        <Factory component={card} />
      ))}
      <button onClick={doStuff}>Click me</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In our App we added a counter to keep track of our changing states. Then in our A, B, C components we add a console statement to keep track of how many times they are rendering like so

function A() {
 console.log("Rendered A");
 return <div>A</div>;
}

function B() {
 console.log("Rendered B");
 return <div>B</div>;
}

function C() {
 console.log("Rendered C");
 return <div>C</div>;
}
...
Enter fullscreen mode Exit fullscreen mode

Now every time we are clicking the button we will see that our A, B, C … components are re-rendering.

[N|Solid]

Now that is a problem. In scale this could cause some pretty nasty user experience especially if we have around 20 something card components and a slow internet connection. Perhaps, we would only want to update 1 or 2 components in some cases and cache the rest of them. So let's take a look how we can cache them with React.memo()

A.jsx

function A(props) {
  console.log("Rendered A");
  return <div>A</div>;
}

const areEqual = (prevProps, nextProps) => {
  return prevProps.name === nextProps.name;
};
const Acached = React.memo(A, areEqual);
Enter fullscreen mode Exit fullscreen mode

Now in our factory we can decide to return the cached version of our component.


function Factory(props) {
 switch (props.component.type) {
   case "A":
+     return <Acached {...props.component} />;
   case "B":
Enter fullscreen mode Exit fullscreen mode

Great!! Now our factory will not re-render A and return a cached version. This could be very powerful when used correctly.
Imagine having a real time component that is refreshing every second. This could lead to very expensive re-rendering. We can limit re-rendering to every 5 seconds or based on whatever logic we wish. In scale this will give us a performance boost. We also encapsulate all our logic in our factory function to better organize our code and separate the concerns.

However, for the sake of this demo we will just add a button in our App to mimic hard reload.

App.jsx

function App() {
  const [count, setCount] = React.useState(0);
+  const [reload, setReload] = React.useState(false);
  const doStuff = () => {
    setReload(false);
    setCount(count + 1);
    console.log(" Click ---> ");
  };

+  const hardReload = () => {
+    setReload(true);
+    console.log(" Hard Reload ---> ");
+  };
  return (
    <div>
      {cards.map(card => (
+        <Factory component={card} hard={reload} />
      ))}
      <button onClick={doStuff}>Click me</button>
+     <button onClick={hardReload}>Hard Reload</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Factory.jsx

function Factory(props) {
  switch (props.component.type) {
+    case "A":
+      if (props.hard) {
+        return <A {...props.component} />;
+      }
+      return <Acached {...props.component} />;

  }
}
Enter fullscreen mode Exit fullscreen mode

Pros and Cons of Factory Pattern

As usual nothing comes without trade-offs :suspect:

Pros Cons
Single Responsibility Principle. You can move the Card creation code into one place in your application, making the code easier to maintain and test. ❌ You need to write extra code at the beginning (i.e. factory function) to set up the factory pattern.
Open/Closed Principle. You can introduce new types of Cards into the application without breaking existing code.

Interested in how to apply Open/Close principle in React?
check this article

That’s all for today. For more articles like these please follow me and leave a comment/like if you have any feedback or input.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .