React Component Factory
The Component Factory is responsible for mapping data to components. It manages rendering various types of data without the need for prior knowledge of the content structures. This mechanism implements a plugin-based architecture, providing flexibility by allowing developers to override or create new plugins based on their projects needs.
Introduction
In modern web development, creating dynamic pages or user interfaces that can adapt to varying types of content is a common requirement, this is especially true with a headless or decoupled application. With a headless or decoupled application developers would typically have to create templates or pages that would handle mapping the data to elements on the page. This is alot of development anytime you want to add content to your site, this is due to the effort on frontend development to devlop a new page or template for the specific content and map the data received to their corresponding elements. In many cases you are losing ability for customization you previously may have had on your application prior to decoupling.
Component Factory helps to solve this issue by allowing a single template or page to handle all possible structures of content that may be received from external systems. Content provided from external services such as a content management system (CMS) almost always means the content will vary in format. Things like text, images, buttons, image carousels or more complex custom structures. In order to render this data effectively, developers need a solution that can dyanmically determine how to present the data without the need of a set of predefined configurations.
Key features
1. Data mapping
The Component Factory is designed to handle the mapping of data to corresponding components. It is capable of rendering diverse data structures by dynamically selecting the appropriate plugin for that specific set of data.
2. Plugin Architecture
One of the most important features of this mechanism is its use of plugins to handle rendering requirements. Developers can use a set of predefined plugins that handle common types of data, develop their own plugins to take full control of their rendering needs, or more likely a combination of predefined and custom plugins working together to broaden the types of data it can render. When a new data type needs to be handled the developer can write a new plugin for the component factory. Limiting the amount of development changes required to handle the newly added data types.
Use Cases
The Component Factory is well-suited for a range of use cases, including but not limited to:
1. Content management systems:
When integrating content from CMS systems, the mechanism enables developers to render the content without prior knowledge of the content's data structure.
2. Dynamic user interfaces:
Building applications that need to render user-generated content where the content can vary will greatly benefit from the flexibility provided by the Component Factory.
3. Faster Development:
For projects where the content types are expected to evolve or where quick adaptability to new data structures is critical. By isolating plugins they become easier to create new, or update existing by only needing to change it once in one location. It also becomes easier to test that given a set of data it is rendered correctly.
Technical Implementation
Usage
Before you can use the Component Factory you will need to instantiate a ComponentFactory by passing a plugins object to its initialization function. You can use it like any other component by passing it data as a prop. The Component Factory will return an array of components, one for each type of data. If the Component Factory doesn't have a registered plugin for some data recieved it returns null for that instance.
import { initComponentFactory } from "@acromedia/gesso";
import { componentFactoryPlugins } from "@acromedia/gesso-drupal"; // Import default plugins as needed. This example we're registering the Drupal plugins
const ComponentFactory = initComponentFactory(componentFactoryPlugins); // Initialize the component factory registering its plugins.
const DynamicPage = () => {
const { payload } = useContent('12')
// In this example the response `payload` contains a field called `fieldComponents` which
// holds an array of content data we're looking to render.
return {
<ComponentFactory data={payload.fieldComponents} />
}
}
Configuration
Each plugin is associated to a unique "type" identifier, which will
be matched to the data's type
field for rendering. The only field required to be in the data received by the component factory is a type
field.
The Component Factory data must adhere to the following structure in order to work.
export interface ComponentFactoryData {
type: {
value: string,
};
[key: string]: unknown;
}
Here we will instantiate the ComponentFactory using predefined plugins specific to drupal, as well as two custom plugins (button, and text).
import { initComponentFactory } from "@acromedia/gesso";
// Import default plugins as needed.
import { componentFactoryPlugins } from "@acromedia/gesso-drupal";
const plugins = {
...componentFactoryPlugins, // Spread the default plugins first.
button: ButtonPlugin, // Add custom plugins second allowing custom plugins to override existing ones if needed.
text: TextPlugin,
};
const ComponentFactory = initComponentFactory(plugins); // Initialize the component factory registering its plugins.
Custom Plugins
In order to render a new type of data a plugin must be defined and registered with the ComponentFactory.
Plugins are fairly simple. Similiar to developing a React Component.
We will create a plugin for data of type Container
.
Each plugin will receive the following two arguments:
data
: this is the data object of the type corresponding to the plugin. IE: the Container plugin receives data of type "container"pluginManager
: This is a function responsible for determining which plugin should handle what data. It's passed into the plugin allowing a plugin to itself dynamically render data that could be included in the original data set. IE: A container is responsible for some layout decisions and contains child components which also need to be rendered within the container. To do this we pass the child data into the pluginManager so it can be run through the component factory for rendering.
In this example let's say the data takes this shape. It contains a single "container", which itself contains data for a single button keyed by "children"
[{
type: {
value: 'container'
},
padding: '25px',
backgroundColor: 'blue',
children: [
{
type: {
value: 'button'
},
variaton: 'link',
url: 'http://acromedia.com'
color: 'orange'
}
]
}]
We will assume a button plugin is already provided and we are implementing the container plugin only.
Create the container plugin following the ComponentFactoryPlugin
interface.
type ComponentFactoryPlugin = (
data: ComponentFactoryData,
pluginManager: PluginManager,
) => JSX.Element | null;
Your plugin receives data
used for mapping, and optionally make use of pluginManager
when your plugin requires rendering of additional children.
const ContainerPlugin = (
data: ComponentFactoryData,
pluginManager: PluginManager
) => {
const { padding, backgroundColor, children } = data;
return (
<Container padding={padding} background={backgroundColor}>
{pluginManager(children)}
</Container>
);
};
In the above example we've created a plugin called ContainerPlugin
which receives data containing 4 fields (type, padding, backgroundColor, children) 3 of which are static and can be extracted and passed to their corresponding props.
As well as more dynamic data (children) that needs to also be dyanmically rendered. We handle this by passing it into the pluginManager
which then runs the logic over again in order to determine how
this data should be rendered. This allows plugins to themselves act as a component factory.
This plugin can now be registered with the component factory as seen above by altering the plugins object to contain the newly created plugin.
const plugins = {
...componentFactoryPlugins,
container: ContainerPlugin, // The data type that `ContainerPlugin` handles is called `container`
};
You can now pass the data described above and see it output the correct elements on your page.
/**
data=[{
type: {
value: 'container'
},
padding: '25px',
backgroundColor: 'blue',
children: [
{
type: {
value: 'button'
},
variaton: 'link',
url: 'http://acromedia.com'
color: 'orange'
}
]
}]
*/
<ComponentFactory data={data} />
Conclusion
The React ComponentFactory is a powerful solution for rendering dynamic content in web applications by facilitating the mapping of various data types without extensive pre-configuration, simplifies development, and promotes code maintainability. This mechanism is particularly valuable when integrating content from diverse sources, such as CMS systems, and when building applications that require adaptability to changing content types. The React ComponentFactory paves the way for scalable, dynamic user interfaces.