Skip to content

Material UI ​

Map Material UI inputs to Formhaus field types through the components prop of FormRenderer.

Open in StackBlitz · Source

Install ​

bash
npm install @formhaus/core @formhaus/react @mui/material @emotion/react @emotion/styled

Component map ​

ts
import type { FieldComponentMap } from '@formhaus/react';
import { AutocompleteField } from './fields/AutocompleteField';
import { CheckboxField } from './fields/CheckboxField';
import { FileField } from './fields/FileField';
import { RadioField } from './fields/RadioField';
import { SelectField } from './fields/SelectField';
import { SwitchField } from './fields/SwitchField';
import { TextField } from './fields/TextField';
import { TextareaField } from './fields/TextareaField';

export const components: FieldComponentMap = {
  text: TextField,
  email: TextField,
  phone: TextField,
  number: TextField,
  password: TextField,
  date: TextField,
  datetime: TextField,
  select: SelectField,
  autocomplete: AutocompleteField,
  multiselect: SelectField,
  checkbox: CheckboxField,
  radio: RadioField,
  switch: SwitchField,
  file: FileField,
  textarea: TextareaField,
};

Text field ​

One renderer covers text, email, phone, number, password, date and datetime fields:

tsx
import type { FieldComponentProps } from '@formhaus/react';
import MuiTextField from '@mui/material/TextField';

export function TextField({
  field,
  value,
  error,
  loading,
  disabled,
  onChange,
}: FieldComponentProps) {
  const inputType =
    field.type === 'email' ? 'email'
    : field.type === 'password' ? 'password'
    : field.type === 'number' ? 'number'
    : field.type === 'date' ? 'date'
    : field.type === 'datetime' ? 'datetime-local'
    : field.type === 'phone' ? 'tel'
    : 'text';

  const shrinkLabel = field.type === 'date' || field.type === 'datetime';

  return (
    <MuiTextField
      id={field.key}
      label={field.label}
      type={inputType}
      value={value != null ? String(value) : ''}
      placeholder={field.placeholder}
      onChange={(e) => onChange(field.type === 'number' && e.target.value !== '' ? Number(e.target.value) : e.target.value)}
      error={!!error}
      helperText={error || field.helperText}
      disabled={disabled || loading}
      fullWidth
      margin="normal"
      slotProps={{
        inputLabel: shrinkLabel ? { shrink: true } : undefined,
      }}
    />
  );
}

App ​

tsx
import { FormRenderer } from '@formhaus/react';
import { Container, CssBaseline, ThemeProvider, createTheme } from '@mui/material';
import { FormActions } from './actions/FormActions';
import { StepProgress } from './actions/StepProgress';
import { components } from './component-map';
import definition from './definition.json';

const theme = createTheme();

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Container maxWidth="sm" sx={{ py: 4 }}>
        <FormRenderer
          definition={definition}
          components={components}
          ActionsComponent={FormActions}
          ProgressComponent={StepProgress}
          onSubmit={(values) => console.log('Submitted:', values)}
        />
      </Container>
    </ThemeProvider>
  );
}

The other renderers are in src/fields and src/actions.

Server re-validation ​

The example validates in the browser only. To check the same rules on the server, build a FormEngine from the same definition and call validate(), then pass the returned errors to the errors prop. See Next.js for a complete route handler.