Relaycode

Struct Input

Composite field group for Substrate struct types with per-field validation

import { Struct } from "@/components/params/inputs/struct";

<Struct
  name="identity"
  label="Identity Info"
  client={client}
  typeId={50}
  fields={[
    {
      name: "display",
      label: "Display Name",
      typeName: "Data",
      component: <TextInput />,
      required: true,
    },
    {
      name: "email",
      label: "Email",
      typeName: "Data",
      component: <TextInput />,
    },
  ]}
  onChange={(val) => console.log("Struct:", val)}
/>

Struct Input

The Struct input renders a group of named fields inside a card, where each field has its own resolved sub-input component. Structs are Substrate's equivalent of named record types -- they appear in extrinsic parameters for multi-field configurations like IdentityInfo, ProxyDefinition, and any call argument that the metadata defines as a Struct TypeDef.

Supported Types

The Struct component sits at priority 35 in the registry with no explicit type name patterns. Instead, it is resolved through the TypeDef fallback mechanism: when no higher-priority pattern matches and the chain metadata reports the type's TypeDef as "Struct", findComponent returns the Struct component.

This means the Struct component handles any type that the metadata declares as a struct, regardless of its name.

Props

The component extends ParamInputProps with a required fields prop:

PropTypeRequiredDescription
namestringYesField identifier used as a prefix for child field IDs
labelstringNoDisplay label shown above the card
descriptionstringNoHelp text shown below the card
typeNamestringNoThe SCALE type name
isDisabledbooleanNoDisables all child inputs
isRequiredbooleanNoShows a red asterisk next to the label
errorstringNoValidation error message to display
clientDedotClient<PolkadotApi>YesConnected Dedot client for metadata
typeIdnumberNoMetadata type ID for this struct
valueanyNoExternally controlled value
onChange(value: unknown) => voidNoCallback fired with the struct record
fieldsStructField[]YesField definitions including pre-resolved components

StructField Interface

interface StructField {
  name: string;
  label: string;
  description?: string;
  typeName?: string;
  component: React.ReactNode;
  required?: boolean;
}

The fields array is injected by the extrinsic builder at render time after resolving each field's component from the chain metadata.

Features

  • Card layout: Fields render inside a Card component with consistent spacing and visual grouping.
  • Per-field components: Each field receives its own pre-resolved React component, which is cloned with the correct name, label, isDisabled, and onChange props.
  • Type badge: Each field label shows a small code badge with its SCALE type name for clarity.
  • Required field validation: Uses validateStructFields to check that all required fields have values, displaying a validation error when they are missing.
  • Composable: Struct fields can themselves be enums, vectors, or other structs, enabling deeply nested composite types.

Type Resolution

import { findComponent } from "@/lib/input-map";

// Struct is resolved via the TypeDef fallback, not by type name patterns.
// When the metadata reports typeId 50 as a Struct:
const resolved = findComponent("SomeCustomStruct", 50, client);
// Returns { component: Struct, schema, typeId: 50 }

Usage

The Struct component is rendered by the extrinsic builder when a parameter's metadata type is a struct. Direct usage requires pre-resolved field components:

import { Struct } from "@/components/params/inputs/struct";

<Struct
  name="identity"
  label="Identity Info"
  client={client}
  typeId={50}
  fields={[
    {
      name: "display",
      label: "Display Name",
      typeName: "Data",
      component: <TextInput />,
      required: true,
    },
    {
      name: "email",
      label: "Email",
      typeName: "Data",
      component: <TextInput />,
    },
  ]}
  onChange={(val) => console.log("Struct:", val)}
/>

Validation

The component uses a Zod schema that expects a record of string keys to any values:

const schema = z.record(z.string(), z.any());

Additionally, validateStructFields checks that all fields marked as required are present and non-undefined in the values record.

Value Format

The onChange callback receives a flat record mapping field names to their values:

// Example: IdentityInfo struct
{
  display: "Alice",
  email: "alice@example.com",
  web: undefined
}

// Example: ProxyDefinition struct
{
  delegate: "5GrwvaEF...",
  proxyType: { type: "Any" },
  delay: "0"
}

Each key corresponds to a StructField.name, and the value is whatever the field's resolved component emits. Fields that have not been filled in will have undefined values.