How to create forms with MUI (material ui)

Viewed 211

Hiho,

I want to use mui in my current react project. Is there a different/better way to create forms than the following example?:

    const [companyName, setCompanyName] = useState<string>("");
    const [companyNameError, setCompanyNameError] = useState<boolean>(false);
    const changeName = (event: React.ChangeEvent<HTMLInputElement>) => {
        if(event.target.value === "") {
            setCompanyNameError(true);
        } else {
            setCompanyNameError(false);
        }

        event.preventDefault();
        setCompanyName(event.target.value);
    }

    const anyInputFieldEmpty = () => {
        var result = false;
   
        if(companyName === "") {
            setCompanyNameError(true);
            result = true;
        } else {
            setCompanyNameError(false);
        }
        
        // add the same check for all other fields. My real code has multiple input fields

        return result;
    }

    const resetFields = () => {
        setCompanyName("");
    }

    return (
        <div>
            <TextField
                required
                fullWidth
                label="Company Name"
                margin="dense"
                name="companyName"
                value={companyName}
                onChange={changeName}
                helperText={companyNameError ? "Company name is not allowed to be empty!" : ""}
                error={companyNameError}
            />

            <Button
                sx={{ alignSelf: 'center', }}
                variant="contained"
                onClick={() => {
                    if(!anyInputFieldEmpty()) {
                        onSubmitClick(); // some function from somewhere else, which triggers logic
                        resetFields(); // This form is in a popover. The values should be resetted before the user open it again.
                    }
                }}
            >
                Create
            </Button>
        </div>);

It feels wrong to do the validation this way if I use multiple textfields (up to 9). Its a lot of boilerplate code and if I add further validation rules (for example a minimum character count) it goes crazy.

Is this the right way to achive my goal?

T

3 Answers

I think you should check the Formik for hook-based validation and jsonforms, react-declarative from json-schema based view creation

Less code solutions is better on production, but for a learning reason it better to write real code based on hooks, contexts or redux reducers

import { useState } from "react";

import { One, FieldType } from "react-declarative";

const fields = [
  {
    type: FieldType.Text,
    title: "First name",
    defaultValue: "Peter",
    name: "firstName"
  },
  {
    type: FieldType.Text,
    title: "Age",
    isInvalid: ({ age }) => {
      if (age.length === 0) {
        return "Please type your age";
      } else if (parseInt(age) === 0) {
        return "Age must be greater than zero";
      }
    },
    inputFormatterTemplate: "000",
    inputFormatterAllowed: /^[0-9]/,
    name: "age"
  }
];

export const App = () => {
  const [data, setData] = useState(null);

  const handleChange = (data) => setData(data);

  return (
    <>
      <One fields={fields} onChange={handleChange} />
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </>
  );
};

export default App;

screenshot

An example project could be found on this codesandbox

MUI does not have a native form validator i recommend using react-hook-form + yup it's pretty simple and has a lot of tutorials

https://react-hook-form.com/get-started

EXEMPLE

TextFieldComponent

import { OutlinedTextFieldProps } from '@mui/material';
import React from 'react';
import { Control, useController } from 'react-hook-form';
import { InputContainer, TextFieldStyled } from './text-field.styles';

export interface TextFieldProps extends OutlinedTextFieldProps {
  control: Control;
  helperText: string;
  name: string;
  defaultValue?: string;
  error?: boolean;
}
export const TextField: React.FC<TextFieldProps> = ({
  control,
  helperText,
  name,
  defaultValue,
  error,
  ...rest
}) => {
  const { field } = useController({
    name,
    control,
    defaultValue: defaultValue || ''
  });
  return (
    <InputContainer>
      <TextFieldStyled
        helperText={helperText}
        name={field.name}
        value={field.value}
        onChange={field.onChange}
        fullWidth
        error={error}
        {...rest}
      />
    </InputContainer>
  );
};

Styles

import { TextField } from '@mui/material';
import { styled } from '@mui/material/styles';

export const TextFieldStyled = styled(TextField)`
  .MuiOutlinedInput-root {
    background: ${({ theme }) => theme.palette.background.paper};
  }
  .MuiInputLabel-root {
    color: ${({ theme }) => theme.palette.text.primary};
  }
`;

export const InputContainer = styled('div')`
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  justify-content: center;
  width: 100%;
`;

export const InputIconStyled = styled('i')`
  text-align: center;
`;

Usage

// Validator
const validator = yup.object().shape({
  email: yup
    .string()
    .required(translate('contact.email.modal.email.required'))
    .email(translate('contact.email.modal.email.invalid')),
});


// HookForm
const {
    control,
    handleSubmit,
    formState: { errors }
  } = useForm({
    resolver: yupResolver(validator)
});

// Compoenent
<TextField
          label="label"
          fullWidth
          placeholder="placeholder
          size="small"
          control={control}
          helperText={errors?.name?.message}
          error={!!errors?.name}
          name={'name'}
          variant={'outlined'}
        />
Related