DEV Community

Cover image for React.js with Factory Pattern ? Building Complex UI With Ease
Shadid Haque
Shadid Haque

Posted on • Updated on

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

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.

Top comments (9)

Collapse
 
click2install profile image
click2install

The factory is an excellent choice but unfortunately, your switch breaks OCP.

Collapse
 
ryskin profile image
Alexey

I see article based on classes but this is too much code, do you have a solution?

Collapse
 
ashkanmohammadi profile image
Amohammadi2

The only other solution that I can think of, is to use an object which has component types as keys and the actual components as values.

Then you can map your component type to the object keys to instantiate different types of components

To add a new type, you'll just need to add another key & value pair to the object

Collapse
 
shadid12 profile image
Shadid Haque

what kind of solution are you looking for ?

Thread Thread
 
click2install profile image
click2install

A strategy pattern would adhere to OCP. You can define you strategies externally and pass them in. Similarly a more correct factory where you can register a key and value allows for no changes to implementation when adding more items.

Thread Thread
 
johannes5 profile image
Johannes5

Could you provide an example of a strategy pattern with react components?

And how would you feed components with different props, when you have a js Map or Object?

Thread Thread
 
shadid12 profile image
Shadid Haque

Good question. This is another blog worthy topic. I'll address it in the next post 😉

Thread Thread
 
johannes5 profile image
Johannes5 • Edited

That's great!

In my case, I used the factory pattern with a component called ItemFactory .
Which makes one of a lot of possible requests using React Query. I have the different request functions dynamically selected inside another module and used inside a single:

useQuery(["itemFactory", sourceCategory, ...args], fetchItemsFromTheCorrectEndpointBasedOn (sourceCategory, ...args))

The sourceCategory also decides (via if-statement) what type of ItemComponent {... withWhatSelectionOfProps} is being rendered.

Collapse
 
thexdev profile image
M. Akbar Nugroho

Factory pattern is cool. I've used it to make my project support multi device code splitting.