Generate a UI Component configuration on the server side (legacy)
This approach will result in poor front-end performance. If you need programmatic constructs like conditionals and iterators to generate your front-end, use React directly for improved performance.
You can use the dynamic component renderer to programmatically create your JSON configuration. This is useful in scenarios where you need to enable or disable certain configurations based on a condition.
This topic covers:
- How to create a dynamic component renderer,
- How to dispatch actions to the component that is rendered dynamically,
- Constraints and limitations.
Before proceeding, it is advisable to consider if you can use one of the alternatives in this document.
If you are new to the platform, see the following links for more context:
- To learn about the standard UI directory, see UI workspace structure and name conventions.
- To learn about conditional rendering, see render components based on a boolean condition.
- To learn about switch rendering, see render components based on an multiple options or enum.
Constraints and limitations
Use UiSdlDynamicComponentRenderer to dynamically construct a UI component configuration on the back end. This approach provides flexibility and lets you leverage the UI Component library using JSON. However, it performs poorly at runtime because it requires multiple round trips between the front end and the back end.
To improve performance, use React directly. React supports programmatic constructs such as if and forEach, which reduce the number of front-end and back-end requests and yield faster runtime performance.
Different behaviors from JSON files
Some component features rely on our code generation steps and since dynamic components are not pre-compiled, those features might not work and they vary component-to-component, making it difficult to document and discover.
One such area is around component references, where our code-gen can infer information statically with our type system and generate code for it, including:
- Data requests - Dynamic components might trigger duplicate requests if their parent already sent them.
- Unique IDs - Dynamic components require an explicit unique ID.
- Default values - Dynamic components' metadata lacks deep default values.
- Static imports of children components - Dynamic components and their children are always dynamically loaded, causing slowness.
Alternatives
Dynamic components offer flexibility but often cause performance issues and complexity. Before using UiSdlDynamicComponentRenderer, consider these alternatives:
- Render components conditionally - Use a conditional renderer to show or hide components based on a boolean expression. See: render or hide components based on a boolean condition
- Switch components based on a value - Use a switch renderer to choose between multiple components. See: render components based on multiple options.
- Change configuration values based on state - Bind application or component state directly to the props of a UI component. See: passing dynamic values from state as props.
- Create your own component - Build a custom component when you need full control over render logic, lifecycle, or prop calculation. See: Application Specific components.
If you use a dynamic component because no other pattern fits, submit an enhancement request to help the UI team support your use case natively in the component library.
Using the dynamic component renderer
This scenario demonstrates how to use UiSdlDynamicComponentRenderer to render a chart showing data from the past x days.
Step 1. Create a Type and add a method as one of its fields
Create a Type named DynamicChartRenderer and declare a method named getDynamicChartConfig.
// {packageName}/src/DynamicChartRenderer.c3typ
type DynamicChartRenderer {
/**
* Function to render a chart that shows data from the previous x days
*/
getDynamicChartConfig: function(dataType: string, entityId: string, metricName: string, daysOffset: int): json js-server
}Step 2. Implement the method referenced in DynamicChartRenderer and return a valid UiSdlComponentRef
To dynamically render components using UiSdlDynamicComponentRenderer, your server-side method must return an object with a children field that contains an array of valid UiSdlComponentRef objects. Each child must:
- Represent a valid UI component definition (as you would define in
ui/c3/meta/{componentId}.json). - Include a unique
idfield. - Match the expected structure of a runtime component JSON definition.
// {package}/src/DynamicChartRenderer.js
function getDynamicChartConfig(dataType, entityId, metricName, daysOffset) {
// Set the current date as the chart's end date
const endDate = new Date();
// Calculate the start date by subtracting the offset in days
const startDate = new Date();
startDate.setDate(startDate.getDate() - daysOffset);
// Define the time interval to use on the chart
const interval = 'DAY';
// Create a UI component configuration object of type UiSdlComponentRef
const chartComponent = {
id: 'examplePackage.DynamicallyRenderedChart', // Required unique component ID
type: 'UiSdlConnected<UiSdlTimeseriesLineBarChart>', // Must match a valid SDL component type
component: {
displayStartDate: startDate, // Set the beginning of the x-axis range
displayEndDate: endDate, // Set the end of the x-axis range
displayInterval: interval, // Display daily intervals
xAxis: {
type: 'UiSdlTimeseriesLineBarChartXAxisStaticConfig', // Use static X-axis configuration
interval: interval, // Set interval granularity
startDate: startDate, // X-axis range start
endDate: endDate, // X-axis range end
},
dataSpec: {
yAxisFields: [{
dataType: dataType, // Type of data to retrieve (e.g., TestMetricEvaluable)
entityId: entityId, // Entity to query (e.g., a wind turbine ID)
metricName: metricName, // Name of the metric to visualize (e.g., RandomChartDataOne)
}],
},
},
};
// Return the chart wrapped inside an array under the 'children' key
return {
children: [chartComponent], // Must be an array of UiSdlComponentRef
};
}Step 3. Render the JSON component configuration
Note: The daysOffset is set to 7.
// <packageName>/ui/c3/meta/examplePackage.dynamicChartExample.json
{
"type": "UiSdlConnected<UiSdlDynamicComponentRenderer>", // Uses the dynamic renderer
"component": {
"dataSpec": {
"dataType": "DynamicChartRenderer", // Type name containing the backend logic
"actionName": "getDynamicChartConfig", // Method to invoke
"actionArgs": {
"dataType": "TestMetricEvaluable",
"entityId": "thing_1",
"metricName": "RandomChartDataOne",
"daysOffset": 7 // Offset in days
}
}
}
}Step 4: Create a Type to add a number input box and an epic to change previous x days
// <packageName>/ui/c3/meta/examplePackage.NumberInputBox.json
{
"trigger" : "examplePackage.NumberInputBox.INPUT_CHANGE",
"effectType": "ChangeChartDateOffsetEpic"
}The previous codeblock refers to the epic that changes the previous x days.
Step 5. Create an epic to target the previous component and change the data source
// <packageName>/src/epics/ChangeChartDateOffsetEpic.c3typ
@typeScript
type ChangeChartDateOffsetEpic mixes UiSdlEpicStep 6. Ensure that the daysOffset updates the dataspec of the correct component. Since we defined the actionArgs
(action arguments) in examplePackage.dynamicChartExample.json, the epic targets this component and updates the data source.
// {package}/src/epics/ChangeChartDateOffsetEpic.ts
import { of, concat } from 'rxjs';
import { Epic } from 'redux-observable';
import { AnyAction } from 'redux';
import { mergeMap } from 'rxjs/operators';
import {
ImmutableReduxState,
getAllComponentDataSourceIds,
} from '@c3/ui/UiSdlConnected';
import {
requestDataAction,
mergeArgumentsAction,
} from '@c3/ui/UiSdlDataRedux';
// Define the epic function that listens for input changes
export const epic: Epic<AnyAction, AnyAction, ImmutableReduxState> = (actionStream, stateStream) => {
return actionStream.pipe(
mergeMap((action) => {
// ID of the component to update
const componentId = "examplePackage.dynamicChartExample.json";
// Get current Redux state
const state = stateStream.value;
// Extract new day offset from the event payload
const dayOffset = Number(action.payload.value);
// Create new arguments to pass to the backend function
const newArgs = {
daysOffset: dayOffset,
};
// Get the first data source ID for the component
const dataSource = getAllComponentDataSourceIds(componentId, state)[0];
// Dispatch actions to update arguments and request new data
return concat(
of(mergeArgumentsAction(dataSource, newArgs, componentId)), // Update dataSpec args
of(requestDataAction(dataSource)) // Trigger new data fetch
);
})
);
};