C3 AI Documentation Home

Limitations of JSON UI components

There are two ways define UI components—one uses declarative JSON, and the other uses programmatic tools like React. Developers can use these approaches separately or combine them within the same application.

Benefits of JSON declarative components

JSON components define user interfaces using a declarative configuration. Developers specify what data to display and how to present it in JSON, rather than writing control flow or rendering logic. When you run the UI bundler, it analyzes all declarative JSON configurations in your application package. It then generates standard React and Redux code to build a complete Node.js, React, and Redux application. The bundler compiles this application into static assets — HTML, CSS, and JavaScript — for deployment.

Declarative components support several built-in capabilities:

  • Fetch backend data automatically using a dataSpec
  • Handle error and loading states without additional logic
  • Accept user input through predefined UI elements
  • Apply theming, localization, and accessibility automatically

The following example configures a basic data grid that displays records from the WindTurbine entity:

JSON
// {package}/ui/c3/meta/examplePackage.TurbineGrid.json

{
  "type": "UiSdlConnected<UiSdlDataGrid>",
  "component": {
    "dataSpec": {
      "dataType": "WindTurbine",
      "columnFields": [
        {
          "fieldName": "location",
          "label": "Turbine location"
        },
        {
          "fieldName": "status",
          "label": "Status"
        }
      ]
    }
  }
}

This configuration renders a data grid with two columns: location and status. The platform handles the data request automatically based on the dataType. There is a rich set of configurations available for each UI Component that application developers can enable by checking the API reference documentation for the particular UI component.

JSON and programmatic constructs

JSON works well for declaring intent, but sometimes your intent depends on context. For example, you might want to render one component instead of another based on a condition. Because JSON is static and declarative, it cannot represent dynamic constructs or runtime logic. As a result, it does not support:

  • Conditional rendering based on arbitrary expressions
  • Iterating over custom collections or computed data
  • Reusing fragments with parameters or injecting child components
  • Managing local or shared component state
  • Changing layout or behavior in response to user interaction

The platform includes UI Components such as UiSdlConditionalRenderer and UiSdlSwitchRenderer to support basic control flow. These UI Components enable simple conditionals but were not designed to replicate the expressive power of code. The goal of JSON UI components is to create simple declarative configurations for situations where you don't need a lot of conditionals.

The following example renders a banner when the current turbine's status is "FAULT":

JSON
{
    "type": "UiSdlConditionalRenderer",
    "condition": {
      "type": "UiDynamicValueRenderCondition",
      "param": {
        "type": "UiSdlApplicationStateValueParam",
        "path": "turbineIsFaulty"
      }
    },
    "useElseComponentAsFallback": true,
    "thenComponent": {
      "id": "examplePackage.FaultyNotificationBanner"
    },
    "elseComponent": {
      "id": "examplePackage.NormalNotificationBanner"
    }
  }

This pattern handles a single boolean condition. However, it does not scale well for complex decision trees, nested conditions, or reusable component patterns. The logic must be embedded directly in JSON and cannot branch, loop, or compose components dynamically.

Composing UI JSON components

To make the JSON configuration experience simple, the platform designs each UI component to include extensive built-in functionality. Each component behaves like a black box and exposes a configuration interface through JSON.

Most JSON UI components were not designed for nesting or composition. Instead of letting you insert arbitrary children, each component defines a fixed structure. If a component does not expose a specific feature through its JSON API, you cannot add that functionality by injecting another component inside it.

This creates a tradeoff between simplicity and flexibility. For example, if you want to insert a custom button inside a filter panel:

  • You can configure it with JSON only if the filter panel component explicitly supports that option.

  • If it doesn’t, and you need fine-grained layout or interactive behavior, you should use React to implement the feature.

Use the table below to compare when to use JSON versus React:

GoalUse JSON UI Component if...Use React if...
Basic layout and display logicThe component supports your use case out of the boxYou need custom structure or layout not exposed in JSON
Reuse with custom childrenYou can configure everything through JSONYou want to insert reusable child components
Injecting interactivity (buttons, actions)The component supports the behavior directly in JSONYou need to add interactive elements not supported by the component
Fine-grained control over layout or behaviorThe layout logic matches the component’s defaultsYou need exact control over structure or interactivity

Why React supports composition

React gives you full control over rendering logic, structure, and behavior. You can define components programmatically, apply conditions, iterate over data, and compose reusable elements. This model allows you to express complex UI patterns that JSON cannot represent.

React components support:

  • All JavaScript expressions, control structures, and functions
  • Modular composition using props and children
  • Local and global state management
  • Integration with Redux, application state, and external libraries
  • You have full control over what the application renders

The following example renders a list of turbines using a reusable child component:

TypeScript
// {package}/ui/c3/src/customInstances/TurbineGrid.tsx

import React from 'react';
import { useC3Action } from '@c3/ui/UiSdlUseC3Action';

// Define a child component for each row
const TurbineRow = ({ turbine }) => {
  // Apply conditional formatting if the turbine is faulted
  const isFaulted = turbine.status === 'FAULT';

  return (
    <div style={{ color: isFaulted ? 'red' : 'black' }}>
      {turbine.name}{turbine.status}
    </div>
  );
};

const TurbineGrid = () => {
  // Fetch turbine data from the backend
  const { status, data } = useC3Action({
    typeName: 'WindTurbine',
    actionName: 'fetch',
    method: 'POST'
  });

  // Show a loading message while the data is being fetched
  if (status !== 'done') return <div>Loading turbines...</div>;

  // Render a list of turbine rows
  return (
    <div>
      {data.map(turbine => (
        <TurbineRow key={turbine.id} turbine={turbine} />
      ))}
    </div>
  );
};

export default TurbineGrid;

This example demonstrates key features of React composition:

  • useC3Action handles backend communication and status tracking
  • The component uses map to iterate through records
  • Each row is rendered using a reusable, parameterized child component (TurbineRow)
  • Conditional styling is applied inline based on the turbine status

Declarative JSON cannot replicate this pattern. It lacks native support for child components, dynamic conditions, and logic-driven rendering.

Comparison: JSON vs. React capabilities

FeatureJSON ComponentsReact Components
Data fetchingBuilt-in via dataSpecCustom with useC3Action, epics
Error/loading handlingBuilt-inFully customizable
Conditional renderingLimited via render conditionsNative via if, ternaries, JSX
Loops and iterationIterator components like Grid and Card listNative via map, for, etc.
Reusable UI fragmentsIterator components like Grid and Card listNative via props and child components
Theming and translationsAutomaticManual or opt-in
Dynamic layout or behaviorIterator components like Grid and Card listSupported through code
Custom interactivity (state)Iterator components like Grid and Card listNative via useState, Redux
Third-party integration (such as MUI)Not supportedSupported through code

Switching from JSON to React

In many applications, developers begin with a JSON layout and later discover the need for more control. Consider this JSON component that renders a simple button:

JSON
{
  "type" : "UiSdlConnected<UiSdlButton>",
  "component" : {
    "content" : "Submit"
  }
}

This setup works for trivial use cases. However, once the button must:

  • Disable based on multiple field values
  • Show a dynamic label
  • Submit a request with runtime arguments
  • Render as part of a form group

then JSON becomes harder to maintain. Switching to React enables all of these capabilities in a single file.

TypeScript
// SubmitButton.tsx

import React from 'react';

const SubmitButton = ({ disabled, label, onSubmit }) => {
  return (
    <button disabled={disabled} onClick={onSubmit}>
      {label}
    </button>
  );
};

This version accepts props, handles logic, and can be reused across many pages.

Using JSON and React together

The platform supports mixed component strategies. You can combine JSON and React on the same page, within the same module, or across different parts of the application.

You can:

  • Reference a React component from JSON using a UiSdlComponentRef
  • Import and render JSON-based components from within a React page
  • Share Redux state and application state across both component types

For example, the following JSON configuration renders a custom React component:

JSON
{
  "type": "UiSdlLayoutNavMenu",
  "navMenu": {
    "id": "App.Nav"
  },
  "children": [
    {
      "id": "App.CustomComponent"
    }
  ]
}

In this pattern, App.CustomComponent is implemented as a .tsx file in the customInstances folder. It does not receive props from JSON. Instead, it must access context, application state, or Redux to manage its behavior.

This hybrid approach allows teams to start with JSON for basic views and incrementally adopt React for custom logic or reusable features. It supports flexibility without requiring a full rewrite.

Was this page helpful?