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.
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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={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><SchemaRendererschema={schema}onSubmit={values => alert(JSON.stringify(values, null, 2))}/></div>);}
On this page