Factory Pattern in React Native without using switch

We can all agree that Factory Pattern is one of the most used design patterns in programming, and in general, one of the best ways to create an object.
However, if you do a simple search for factory pattern examples online, chances are you’ll find an implementation that is based on conditional statements, in other words, if-else or switch statements. If you’re comfortable using switch throughout your app, that’s completely fine, but if you want to take a closer look at the pitfalls of this approach, then keep reading.
First, an example
We will start with a concrete example in React Native that shows a Factory Pattern implementation using a switch statement. We will then look at potential issues that can arise, and how we can modify this code to be more maintainable by removing the switch and implementing the Factory Pattern in a different way.
Let’s say we need to implement a screen where users can enter their info using different React Native input components. The list of components is received from the server, meaning, it can be changed at any time dynamically. For the sake of brevity, in this example we will assume that only the following four fields are received: Username, Password, Birthday and Gender. We will also assume that we have already created matching React Native components for these fields, called <Username>
, <Password>
, <Birthday>
and <Gender>
, respectively.
The (simplified) JSON response received from server could look like this:
{
"items": [
{
"id": "item-id-1",
"type": "username",
"placeholder": "Please enter your username",
"required:": true,
"minLength": 3,
"maxLength": 15
},
{
"id": "item-id-2",
"type": "password",
"required:": true,
"placeholder": "Please enter your password",
"minLength": 8,
"maxLength": 64
},
{
"id": "item-id-3",
"type": "birthday",
"required:": false,
"placeholder": "Please enter your birthday",
"minDate": "1900-01-01",
"maxDate": "2020-01-01"
},
{
"id": "item-id-4",
"type": "gender",
"required:": false,
"placeholder": "Please enter your gender",
"options": [
"male",
"female"
]
}
]
}
The app makes a decision on which component it will use by looking at the type
property.
This is a typical factory based on the switch statement:
function Factory(item) {
switch (item.type) {
case 'username':
return <Username item={item} />;
case 'password':
return <Password item={item} />;
case 'birthday':
return <Birthday item={item} />;
case 'gender':
return <Gender item={item} />;
default:
return null;
}
}
And this factory can be called in the screen render method like this:
render() {
const { items } = this.props;
const components = items.map(item => <Factory item={item} />);
return (
<View>
{components}
</View>
);
}
Very simple and to the point, right? So, what exactly is the problem here? Let’s dig more and find out.
Why the hate on switch?
First of all, let’s see what is wrong with using the switch in the first place.
In many cases, there’s nothing wrong at all! Switch is just a language construct, and all language constructs can be thought of as tools to get a job done. Some tools are better suited to one task than another, but the really important part is how “getting the job done” is defined. Does it need to be maintainable? Does it need to be fast? Does it need to scale? Does it need to be extendable? Does it need to be simple?
Just because switch statements are sometimes a problem doesn’t mean that they always are. Sometimes your task is so simple that avoiding switch can, in fact, make your code more complicated rather than less. Also, sometimes there simply isn’t a better solution. In a procedural language such as C, switch is better than any of the alternatives. But in an object-oriented language, there are other alternatives that better utilise the object structure.
One problem with switch is that it violates the Open/Close principle. This principle states that your code entities (classes, modules, functions) should be open for extension, but closed for modification, which means you should write your code so you will be able to add new functionality without changing the existing code. This prevents situations in which a change to one of your classes also requires you to adapt all depending classes. Typically, similar switch statements are scattered throughout the project. If you add or remove a clause in one switch, you have to hunt down all the other ones and change them as well.
Another problem is size and scalability of your code. If your factory contains just a few different types of objects and is not expected to grow, switch can do just fine, and the alternatives will only bloat your code with a lot of boilerplate. But if you expect many different types of objects in the future, you should definitely reconsider the approach. Not only the logic inside the switch statement will get harder and harder to read, but each new case will also add redundant lines of code such as case A:
, case B:
, break;
or return;
, so you’ll end up with a large, unmaintainable and just plain ugly chunk of code. And good luck if you don’t use a linter and forget to add a break;
in one case! Every method in your code should be as small as possible. Having to scroll through dozens (or hundreds) of conditionals is not user friendly and can be confusing for others too.
Just for fun, let’s imagine that our JSON response from the example above had way more fields, each one requiring its own React Native component. The factory method would look like this:
function Factory(item) {
switch (item.type) {
case 'username':
return <Username item={item} />;
case 'password':
return <Password item={item} />;
case 'birthday':
return <Birthday item={item} />;
case 'gender':
return <Gender item={item} />;
case 'firstName':
return <FirstName item={item} />;
case 'lastName':
return <LastName item={item} />;
case 'email':
return <Email item={item} />;
case 'country':
return <Country item={item} />;
case 'city':
return <City item={item} />;
case 'avatar':
return <Avatar item={item} />;
case 'coverImage':
return <CoverImage item={item} />;
case 'about':
return <About item={item} />;
case 'phone':
return <Phone item={item} />;
case 'website':
return <Website item={item} />;
case 'favoriteColor':
return <FavoriteColor item={item} />;
case 'company':
return <Company item={item} />;
case 'facebook':
return <Facebook item={item} />;
case 'twitter':
return <Twitter item={item} />;
case 'snapchat':
return <Snapchat item={item} />;
case 'tiktok':
return <TikTok item={item} />;
case 'instagram':
return <Instagram item={item} />;
case 'Linkedin':
return <Linkedin item={item} />;
default:
return null;
}
}
Looking more and more scary, doesn’t it? 😉 And, it will look even scarier with additional parameters passed to each component, such as new properties and event listeners responsible for editing each field.
So, what do we do now?
Let’s refactor this code. First, we’ll create a base Field
class for all field components.
class Field extends Component {
constructor({ type }) {
super();
this.type = type;
}
}
export default Field;
Next, we will create a factory for each of the fields:
class Username extends Field {
const { item } = this.props;
render() {
...
}
}
class UsernameFactory {
get type() { return 'username'; }
create({ item }) {
return <Username item={item} />;
}
}
export default UsernameFactory;
class Password extends Field {
const { item } = this.props;
render() {
...
}
}
class PasswordFactory {
get type() { return 'password'; }
create({ item }) {
return <Password item={item} />;
}
}
export default PasswordFactory;
class Birthday extends Field {
const { item } = this.props;
render() {
...
}
}
class BirthdayFactory {
get type() { return 'birthday'; }
create({ item }) {
return <Birthday item={item} />;
}
}
export default BirthdayFactory;
class Gender extends Field {
const { item } = this.props;
render() {
...
}
}
class GenderFactory {
get type() { return 'gender'; }
create({ item }) {
return <Gender item={item} />;
}
}
export default GenderFactory;
Each one of these factories is completely responsible for handling everything related to its field. You will put all UI code here, as well as the event listeners which are different for every field. There is no need to scatter the field-specific code anywhere else in the app, and it’s very easy to add and remove fields.
Now, let’s connect everything by creating the controller classes for the factory:
class FactoryMapper {
constructor() {
const usernameFactory = new UsernameFactory();
const passwordFactory = new PasswordFactory();
const birthdayFactory = new BirthdayFactory();
const genderFactory = new GenderFactory();
this.factories = {};
this.factories[usernameFactory.type] = usernameFactory;
this.factories[passwordFactory.type] = passwordFactory;
this.factories[birthdayFactory.type] = birthdayFactory;
this.factories[genderFactory.type] = genderFactory;
}
factory = type => type && this.factories[type];
}
export default FactoryMapper;
class Factory {
constructor() {
this.factoryMapper = new FactoryMapper();
}
create({ item }) {
const { type } = item;
const factory = this.factoryMapper.factory(type);
return factory.create({ item });
}
}
export default Factory;
And that’s it! You can use the factory in your screen component like this:
class Screen extends Component {
constructor() {
super(props);
this.factory = new Factory();
}
render() {
const { items } = this.props;
const components = items.map(item => this.factory.create({ item });
return (
<View>
{components}
</View>
);
}
}
Now each time you need to add a new type of component, you can create its own factory and link it in the FactoryMapper class. This makes your code easily readable and allows you to add or remove components very easily, without fear of breaking the existing code.
This approach might seem to have too much boilerplate code at first, but remember, it’s all about scaling, and when you finish the initial setup described here, maintaining this code is a breeze, as it requires very little effort in order to add or remove components.
All this doesn’t mean switches are wrong by definition. It just means that you need to be extra careful and think twice about using them. Switch statements are not bad, they are just not always very object-oriented. You don’t have to use object-oriented programming to solve your problem, but if you do use OOP, then switches are something you need to give extra attention to.
If you plan to support only a couple of components, by all means, stick to switch-based factories. But if you’re going to support a huge number of dynamically generated components, this is a perfect tool for your arsenal! 😎