Skip Navigation
React Magma

Schema Renderer

SchemaRenderer is deprecated.

The SchemaRenderer is an advanced component which maps other React Magma components to a defined Schema. This allows developers to render data driven pages and forms at runtime.

The Schema Renderer is currently early beta. It is a work in progress and may have breaking changes.

Basic Template

Several Basic Components from react-magma-dom are mapped in react-magma-schema-renderer by default. Refer to each component api page to find the props that each component supports.

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
const schema: Schema = {
title: 'Basic Components',
type: templateTypes.BASIC,
fields: [
{
component: componentTypes.BANNER,
name: 'basic-banner',
children: 'This is a data driven Banner',
},
{
component: componentTypes.HEADING,
name: 'basic-heading',
children: 'Data Driven Heading',
level: 2,
},
{
component: componentTypes.PARAGRAPH,
name: 'basic-paragraph',
children: 'This is a data driven Paragraph.',
},
{
component: componentTypes.ALERT,
name: 'basic.alert',
children: 'This is a data driven Alert',
variant: 'warning',
},
{
component: componentTypes.HYPERLINK,
name: 'basic.hyperlink',
to: 'https://react-magma.cengage.com',
children: 'This is a data driven link',
},
],
};
export function Example() {
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Form Template

The SchemaRenderer supports data-driven forms. This allows the creation of fully feature forms without having to write custom logic for validation and submission.

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
import { InputType } from 'react-magma-dom';
const schema: Schema = {
title: 'Basic Form',
description: 'A collection of form components.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.INPUT,
name: 'form-input',
labelText: 'Input',
type: InputType.text,
},
{
component: componentTypes.RADIO,
name: 'form-radio',
labelText: 'Radio',
options: [
{ labelText: 'one', value: '1' },
{ labelText: 'two', value: '2' },
{ labelText: 'three', value: '3' },
],
},
{
component: componentTypes.CHECKBOX,
name: 'form-checkbox',
labelText: 'Checkbox',
value: 'checked',
},
{
component: componentTypes.CHECKBOX,
name: 'form-checkbox-group',
labelText: 'Checkbox group',
options: [
{ labelText: 'one', value: '1' },
{ labelText: 'two', value: '2' },
{ labelText: 'three', value: '3' },
],
},
{
component: componentTypes.COMBOBOX,
name: 'form-combobox',
labelText: 'Combobox',
options: [
{ labelText: 'one', value: '1' },
{ labelText: 'two', value: '2' },
{ labelText: 'three', value: '3' },
],
},
{
component: componentTypes.COMBOBOX,
name: 'form-combobox-multi',
labelText: 'Multi combobox',
isMulti: true,
options: [
{ labelText: 'one', value: '1' },
{ labelText: 'two', value: '2' },
{ labelText: 'three', value: '3' },
],
},
{
component: componentTypes.DATE_PICKER,
name: 'form-date-picker',
labelText: 'Date picker',
},
{
component: componentTypes.PASSWORD_INPUT,
name: 'form-password-input',
labelText: 'Password input',
},
{
component: componentTypes.SELECT,
name: 'form-select',
labelText: 'Select',
options: [
{ labelText: 'one', value: '1' },
{ labelText: 'two', value: '2' },
{ labelText: 'three', value: '3' },
],
},
{
component: componentTypes.SELECT,
name: 'form-select-multi',
labelText: 'Multi-select',
isMulti: true,
options: [
{ labelText: 'one', value: '1' },
{ labelText: 'two', value: '2' },
{ labelText: 'three', value: '3' },
],
},
{
component: componentTypes.TEXTAREA,
name: 'form-textarea',
labelText: 'Textarea',
},
{
component: componentTypes.TIME_PICKER,
name: 'form-time-picker',
labelText: 'Time picker',
},
{
component: componentTypes.TOGGLE,
name: 'form-toggle',
labelText: 'Toggle',
},
{
component: componentTypes.SPY,
name: 'form-spy',
subscription: { values: true },
template: ({ values }) => (
<pre>{JSON.stringify({ values }, null, 2)}</pre>
),
},
],
};
export function Example() {
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Sub Forms

Instead of passing children some components support sub fields. This allows for an array of fields to be passed in and render as the children.

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
import { InputType } from 'react-magma-dom';
export function Example() {
const schema: Schema = {
title: 'Sub Form',
description: 'A form within a form.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.FORM_GROUP,
name: 'sub-form-group',
labelText: 'Register',
fields: [
{
component: componentTypes.INPUT,
name: 'sub-name',
labelText: 'Name',
type: InputType.text,
},
{
component: componentTypes.INPUT,
name: 'sub-email',
labelText: 'Email',
type: InputType.text,
},
{
component: componentTypes.PASSWORD_INPUT,
name: 'sub-password',
labelText: 'Password',
},
],
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Field Array

The SchemaRenderer supports field arrays for repeating groups of inputs.

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
import { InputType } from 'react-magma-dom';
export function Example() {
const schema: Schema = {
title: 'Field Array',
description: 'What becomes a repeating subform.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.FIELD_ARRAY,
name: 'field-array',
labelText: 'Field_array',
fields: [
{
component: componentTypes.INPUT,
name: 'field-array-name',
labelText: 'Input',
type: InputType.text,
},
],
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Custom Component

The SchemaRenderer

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
import { Button, ButtonType, Toast } from 'react-magma-dom';
export function Example() {
const [isOpen, setIsOpen] = React.useState(false);
const [showToast, setShowToast] = React.useState(false);
const CustomButton = ({ labelText, ...props }: any) => (
<Button type={ButtonType.button} {...props}>
{labelText}
</Button>
);
const CustomToast = ({ showToast, ...props }: any) => (
<>{showToast && <Toast {...props} />}</>
);
const schema: Schema = {
title: 'Custom Component',
description: 'The most simplest of forms.',
type: templateTypes.BASIC,
fields: [
{
component: componentTypes.MODAL,
name: 'custom-name',
isOpen,
onClose: () => setIsOpen(false),
header: 'Data Driven Modal',
fields: [
{
component: componentTypes.PARAGRAPH,
name: 'custom-modal-name',
children: 'Hello. This is a Paragraph inside a Modal.',
},
],
},
{
component: componentTypes.CUSTOM,
name: 'custom-toast',
onDismiss: () => setShowToast(false),
showToast,
customComponent: CustomToast,
children: 'data driven toast.',
},
{
component: componentTypes.CUSTOM,
name: 'custom-button-modal',
onClick: () => setIsOpen(true),
disabled: isOpen,
labelText: 'OPEN MODAL',
customComponent: CustomButton,
},
{
component: componentTypes.CUSTOM,
name: 'custom-button-toast',
onClick: () => setShowToast(true),
disabled: showToast,
labelText: 'OPEN Toast',
customComponent: CustomButton,
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Conditionals

Fields can be rendered/visable based on conditions. Please see the data-driven-forms documentation for more examples.

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
import { InputType } from 'react-magma-dom';
export function Example() {
const schema: Schema = {
title: 'Conditional Fields',
description: 'Fields may come. Fields may go.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.INPUT,
name: 'conditional-name',
labelText: 'What is your name?',
helperMessage: 'Try to be Andy',
type: InputType.text,
},
{
component: componentTypes.TOGGLE,
name: 'conditional-toggle',
labelText: 'Are you sure?',
condition: {
when: 'conditional-name',
is: 'Andy',
},
},
{
component: componentTypes.TOGGLE,
name: 'conditional-toggle-two',
labelText: 'Are you really sure?',
condition: {
when: 'conditional-toggle',
is: true,
},
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Form Spy

SchemaRenderer has a mapped component to register a Form Spy. This component will rerender whenever any of the state that it is subscribed to changes (which defaults to everything).

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
export function Example() {
const schema: Schema = {
title: 'Form Spy',
description: 'Registering to the form state.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.INPUT,
name: 'spy-input',
labelText: 'Some input',
},
{
component: componentTypes.SPY,
name: 'spy-spy',
template: formState => <pre>{JSON.stringify(formState, null, 2)}</pre>,
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Validators

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
validatorTypes,
} from '@react-magma/schema-renderer';
import { InputType } from 'react-magma-dom';
export function Example() {
const schema: Schema = {
title: 'Field Validation',
description: 'Tossing some validation on an input.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.INPUT,
name: 'validator-input',
labelText: 'Some input',
helperMessage: 'Value must be equal to Foo',
validate: [
{
type: validatorTypes.PATTERN,
pattern: /^Foo$/i,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-length',
labelText: 'Length field',
helperMessage: 'min 5 characters, max 10',
validate: [
{
type: validatorTypes.MIN_LENGTH,
threshold: 5,
},
{
type: validatorTypes.MAX_LENGTH,
threshold: 10,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-exact-length',
labelText: 'Exact length field',
helperMessage: 'must be 3 characters long',
validate: [
{
type: validatorTypes.EXACT_LENGTH,
threshold: 3,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-number-size',
labelText: 'Number value validator',
helperMessage: 'Value of the number must be between 1.36 and 33.3',
type: InputType.number,
validate: [
{
type: validatorTypes.MIN_NUMBER_VALUE,
includeThreshold: true,
value: 1.36,
},
{
type: validatorTypes.MAX_NUMBER_VALUE,
includeThreshold: false,
value: 33.3,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-required-field',
labelText: 'Required field',
required: true,
helperMessage: '* Required',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-url-default-config',
labelText: 'Default validator',
helperMessage:
'type some address like: https://react-magma-cengage.com/',
validate: [
{
type: validatorTypes.URL,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-url-protocol-config',
labelText: 'Custom protocol',
helperMessage:
'type some address with custom gopher protocol like: gopher://react-magma.cengage.com/',
validate: [
{
type: validatorTypes.URL,
protocol: 'gopher',
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-url-without-config',
labelText: 'Without protocol',
helperMessage: 'type some address with like: react-magma.cengage.com/',
validate: [
{
type: validatorTypes.URL,
protocolIdentifier: false,
},
],
},
{
component: componentTypes.INPUT,
name: 'validator-custom-message',
labelText: 'Age (with custom error message)',
type: InputType.number,
helperMessage: 'Enter an age greater than 12',
validate: [
{
type: validatorTypes.MIN_NUMBER_VALUE,
includeThreshold: true,
value: 18,
message:
'You must be at least 12 years old to ride this attraction',
},
],
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Custom Validators

As validator you can provide your custom function. This function takes value, allValues and meta as arguments and returns the error message when it fails, otherwise it returns undefined. Please see final form validate prop.

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
export function Example() {
const schema: Schema = {
title: 'Field Validation',
description: 'Tossing some validation on an input.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.INPUT,
name: 'custom-validator',
label: 'Custom validator',
helperMessage: 'Type name Andy to fail validation.',
validate: [
value => (value === 'Andy' ? 'Andy is not allowed' : undefined),
],
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

Async Validators

You can use a Async function as a validator. But it must be first in the validate array. Other async validators will be ignored. This rule was created to prevent long asynchronous validation sequences.

You can either use custom function, or custom validator from validator mapper.

Validator inputs and results are being cached so you will get immediate feedback for recurring values before the validation is actually finished.

If you do not want to trigger the async validator after every stroke, you can use a debounce promise library (or any other implementation of debouncing.)

import React from 'react';
import {
SchemaRenderer,
Schema,
templateTypes,
componentTypes,
} from '@react-magma/schema-renderer';
const mockEndpoint = value =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (value === 'Andy') {
reject({ message: 'Andy is not allowed' });
}
resolve({ message: 'validation sucesfull' });
}, 2000);
});
const asyncValidator = value =>
mockEndpoint(value)
.catch(({ message }) => {
// error must only throw valid react child eg: string, number, node, etc.
throw message;
})
.then(() => {
// possible success handler
});
export function Example() {
const schema: Schema = {
title: 'Field Validation',
description: 'Tossing some validation on an input.',
type: templateTypes.FORM,
fields: [
{
component: componentTypes.INPUT,
name: 'async-validator',
label: 'Async validator',
helperMessage:
'Type name Andy to fail validation. Validation will take 2 seconds.',
validate: [asyncValidator],
},
],
};
return (
<div>
<SchemaRenderer
schema={schema}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
/>
</div>
);
}

On this page

Deploys by Netlify