C3 AI Documentation Home

Generate a UI Component configuration on the server side (legacy)

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:

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:

  1. Data requests - Dynamic components might trigger duplicate requests if their parent already sent them.
  2. Unique IDs - Dynamic components require an explicit unique ID.
  3. Default values - Dynamic components' metadata lacks deep default values.
  4. 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:

  1. 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
  2. Switch components based on a value - Use a switch renderer to choose between multiple components. See: render components based on multiple options.
  3. 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.
  4. Create your own component - Build a custom component when you need full control over render logic, lifecycle, or prop calculation. See: Application Specific components.

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.

Type
// {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 id field.
  • Match the expected structure of a runtime component JSON definition.
JavaScript
// {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.

JSON
// <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

JSON
// <packageName>/ui/c3/meta/examplePackage.NumberInputBox.json
{
  "trigger" : "examplePackage.NumberInputBox.INPUT_CHANGE",
  "effectType": "ChangeChartDateOffsetEpic"
}

Step 5. Create an epic to target the previous component and change the data source

Type
// <packageName>/src/epics/ChangeChartDateOffsetEpic.c3typ

@typeScript
type ChangeChartDateOffsetEpic mixes UiSdlEpic

Step 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.

TypeScript
// {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
      );
    })
  );
};
Was this page helpful?