Better Programming

Advice for programmers.

Follow publication

Extendable Factory Pattern for React Using TypeScript

Marius Butz
Better Programming
Published in
5 min readDec 24, 2021

At first, I want to give a quick introduction to the factory design pattern. When using different classes of the same type of “thing”, it’s useful to abstract the instantiation of those classes into the factory class/function. While those factories are written by the developer of a library/module, users of those libraries can’t simply add or override classes used by the factory. Therefore, we need a simple solution to allow end developers to extend a factory class. To explain this pattern practically,

I will use the react library react-chat-module, which can be found here. This kind of tutorial is of course transferable to other programming languages, which support reflection (C#, Java, …), see an example in C# at the end.

Use Case

As mentioned, I will explain the idea using the library react-chat-library. This library allows a developer to implement a simple and flexible chat right inside his own react application only with one component.

Like many other chatting or messaging applications, several types of messages (text, audio, video, image, …) are supported.

To implement those different message types a MessageFactory was created, which instantiates the correct message type. For example, this factory could look like this:

Don’t worry, we’ll clean up this code in a few minutes, but you can see, that a react component is returned depending on the MessageType.

The MessageType is represented as a union type of supported message types (typing, text, image, video, audio, and file in this case).

All messages except typing and text can also have a text message (e.g. a label for an image), these two message types are then combined in a sub-element.

Instead of a static method, you can also implement the factory as a functional component, so you can ease it like every other component. As you can see, the type of message is strongly typed using the union type but is not extendable by users of the library.

To fix this problem, we are facing two different subproblems: how to extend the union type and how to pass additional classes, which should be instanced or overridden by the factory?

Enlarging the union type

To extend types declared in a module, TypeScript has a feature called Module Augmentation. The only problem is, that union types can’t be augmented or merged. To work around this problem, we can define an interface, from which we generate our union type:

The initial value of MessageType is the same, as in the example before, except, that we generate it using the interface.

With that setup, we can now augment the MessageType interface within another module (e.g. in our application, when we want to embed the component):

This will add a new type “test” to our union type and we can add it as a type in our ChatMessage interface.

Extending the factory

Now it’s time to start with the tricky part: extending our factory to provide class override or addition. At first, I created a type to add as a prop, where new or overriding factory classes can be defined:

The hasText element in the interface is highly application-related, so it may be unnecessary for your task.

As you can see, I defined a record consisting of a string (the identifier of the new type, MessageType in this case) and react component which should be used instead of or in addition (this works with another kind of class too and isn’t working with react only).

We can then add a function type, which will create our instances of the factory classes:

After that, we have to add two (arrow) functions: the first to create dynamic factory instances and the second to create the predefined ones. Those functions may look like this:

You can see in the function for the dynamic factory instantiation, that we first check, if the provided MessageType exists in the object of provided custom factories, if not null is returned.

If there is a custom factory class for the given type, we create an instance of the provided component using React.createElement (this is working for “normal” classes as well with reflection, see here for further information).

In the function to create the predefined factory class instances, the main code remains the same, except that all code paths return an object consisting of the component and whether to add a text component or not.

At the end of the function, I added a debug warning which gets displayed when the user added a message with a custom type but didn’t define a factory class. We can combine those two functions in the functional component MessageType like this:

As you can see, we first call the function for the custom factory class, to achieve the overriding functionality.

For adding a text message I used a recursive call to the same component, which gets added if the component type supports text (which is defined in the return or the CustomFactory object), this may be obsolete for your use case.

Clean up

The last thing, we want to do, is to get rid of this very ugly if cascade. To do so, we add an object of type CustomFactories and assign all of our default factory classes, then we can remove the function CreateFactoryInstance and instead, call CreateDynamicFactoryInstance with a merged object of our default classes and the overrideFactory object, which looks like that:

How to extend the factory

To use the component and extend the factory, you can do it like this:

And that’s it! Now you can create libraries or packages using the factory design pattern and the possibility to let the users of your module have the opportunity to extend or override your implemented library.

Example in C#

To implement a similar solution of the given example in C#, we can use a class like this:

In this example, we have nearly the same starting point as in the TypeScript version. We got many different implementations of Message (in this example only MessageImage and MessageVideo) and want to create a dynamic instance, based on the type of Message we exactly want to create.

The main class Factory contains a public member DefaultFactories, which contains the default factory class specifications (in this example, to create a MessageImage or MessageVideo object) by storing a type reference. In the constructor of the Factory, the end-user has the possibility to provide a dictionary with additional or replacing class references.

To get an instance by respecting our condition, that the custom factory classes always have higher priority, we combine both dictionaries, but first, remove all keys of the DefaultFactories which are also provided in the additionalFactories.

After that, we can check if we can instantiate the provided type (and throw an exception if not) and create an instance, which we can return.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Write a response