Dynamic Component Renderer
IMPORTANT: Dynamically rendered components have poorer performance than pre-compiled components and might behave differently, see the "Constraints and limitations" and "Alternatives" sections below.
You can use the dynamic component renderer to update JSON configuration based on the results of an API call or other application logic.
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 C3 AI, 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
UiSdlDynamicComponentRenderer provides a lot of flexibility but it comes at a cost. Pre-compiled code from static JSON files can be further optimized and its performance is a core goal of the C3 AI UI code generation, whereas dynamic components are provided as a stop-gap solution where static JSON files fall short.
Performance limitations
When a page includes dynamic components, the page load time will significantly increase because it requires loading code at runtime which hasn't been optimized and might load multiple copies of the same modules and several unnecessary modules.
Pages that are pre-compiled from JSON files at bundle time generate bundles of code specifically for the page, minimizing duplication and unnecessary modules, thus allowing your application to render faster.
Multiplication of data requests
Because dynamic components load code at runtime, they can get into race conditions where the pre-compiled code might send requests that the dynamic component will replicate as part of its initialization.
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
Since dynamic components can be problematic, it is important to know what patterns can be achieved without using them and how to communicate with the UI Team so we introduce new features that enable dynamism.
- Showing different components dynamically - In some cases you might want to render or hide components based on a boolean condition or render components based on multiple options.
- Change configuration values based on state - When you want data stored in your Application state or other component's state to determine the props of a component, explore passing dynamic values from state as props.
- Create your own component - In cases where you want full control of what is rendered, the component lifecycle, or calculating dynamic props, create Application Specific components.
Important: if you are forced to create your own components or cannot use any alternative please make sure you create improvement tickets for your use cases so that they can be captured by our UI Component Library or our UI infrastructure.
Using the dynamic component renderer
Scenario: To render charts with data from the previous days.
Create a C3 AI Type and add a method as one of its fields.
- In the following example, the
DynamicChartRendererType is created and hasgetDynamicChartConfigmethod as a field. - The relative path is:
<packageName>/src/DynamicChartRenderer.c3typ.
Typetype 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 }- In the following example, the
Implement the method referenced in
DynamicChartRendererand return the correct JSON.- The relative path is:
<packageName>/src/DynamicChartRenderer.js.
JavaScriptfunction getDynamicChartConfig(dataType, entityId, metricName, daysOffset) { let endDate = new Date(); let startDate = new Date(); startDate.setDate(startDate.getDate() - daysOffset); const interval = 'DAY'; let chartConfig = { id: 'SDLDemo.DynamicallyRenderedChart', type: 'UiSdlConnected<UiSdlTimeseriesLineBarChart>', component: { displayStartDate: startDate, displayEndDate: endDate, displayInterval: interval, xAxis: { type: 'UiSdlTimeseriesLineBarChartXAxisStaticConfig', interval: interval, startDate: startDate, endDate: endDate, }, dataSpec: { yAxisFields: [{ dataType: metricType, entityId: entityId, metricName: metricName, }], }, }, }; return { children: [chartConfig] }; }- The relative path is:
Render the JSON component configuration.
- Note: The
daysOffsetis set to 7. - The relative path is:
<packageName>/ui/c3/meta/SDLDemo.dynamicChartExample.json.
JSON{ "type" : "UiSdlConnected<UiSdlDynamicComponentRenderer>", "component" : { "dataSpec" : { "dataType" : "DynamicChartRenderer", "actionName" : "getDynamicChartConfig", "actionArgs" : { "dataType": "TestMetricEvaluable", "entityId": "thing_1", "metricName": "RandomChartDataOne", "daysOffset": 7 } } } }- Note: The
Create a Type to add a number input box and an epic to change previous x days.
- The relative path is for the Type is:
<packageName>/ui/c3/meta/SDLDemo.NumberInputBox.json.
JSON{ "trigger" : "SDLDemo.NumberInputBox.INPUT_CHANGE", "effectType": "ChangeChartDateOffsetEpic" }Note: The previous codeblock refers to the epic that changes the previous x days.
- The relative path is for the Type is:
Create an epic to target the previous component and change the data source.
- The relative path is:
<packageName>/src/epics/ChangeChartDateOffsetEpic.c3typ
Type@typeScript type ChangeChartDateOffsetEpic mixes UiSdlEpic- The relative path is:
Ensure that the
daysOffsetupdates the dataspec of the correct component. Since we defined theactionArgs(action arguments) inSDLDemo.dynamicChartExample.json, the epic targets this component and updates the data source.- The relative path is:
<packageName>/src/epics/ChangeChartDateOffsetEpic.ts.
TypeScriptimport { 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'; export const epic: Epic<AnyAction, AnyAction, ImmutableReduxState> = (actionStream, stateStream) => { return actionStream.pipe( mergeMap((action) => { const componentId = "SDLDemo.dynamicChartExample.json"; const state = stateStream.value; const dayOffset = Number(action.payload.value); const newArgs = { daysOffset: dayOffset, }; const dataSource = getAllComponentDataSourceIds(componentId, state)[0]; return concat( of(mergeArgumentsAction(dataSource, newArgs, componentId)), of(requestDataAction(dataSource)) ); }) ); };- The relative path is:
Note: If you would like to change a configuration that is not exposed as an API option (meaning from the return values of getDynamicChartConfig), target the chart component ID (SDLDemo.DynamicallyRenderedChart) instead.