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"
}
]
}
Based on the type of object we will render a card component. Let’s imagine that this is what our users would see:
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() {
...
}
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>
)
}
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.
Can you apply SOLID principles to your React applications ?
Shadid Haque ・ Oct 26 '19
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>;
}
}
And our App
now looks like this.
return (
<div>
{cards.map(card => (
<Factory component={card} />
))}
</div>
);
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>
);
}
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>;
}
...
Now every time we are clicking the button we will see that our A, B, C … components are re-rendering.
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);
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":
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>
);
}
Factory.jsx
function Factory(props) {
switch (props.component.type) {
+ case "A":
+ if (props.hard) {
+ return <A {...props.component} />;
+ }
+ return <Acached {...props.component} />;
}
}
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
Why apply Open/Closed principles in React component composition?
Shadid Haque ・ Oct 29 '19
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)
The factory is an excellent choice but unfortunately, your
switch
breaks OCP.I see article based on classes but this is too much code, do you have a solution?
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
what kind of solution are you looking for ?
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.
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?
Good question. This is another blog worthy topic. I'll address it in the next post 😉
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.
Factory pattern is cool. I've used it to make my project support multi device code splitting.