Context
SSD uses Formik as the main library for managing forms in React, with MUI (Material UI) Components that have been customized to work with Formik already in the Template at src/app/modules/_common/components/CustomFormik can be used directly, and if a new custom form component must be created, it must follow the specified pattern
Main rules
- Formik must be used for every form that has a submit
- The type of form values must always be declared and exported
- Must include
enableReinitialize: truealways when fetching initial data from the API - The validate function can be separated out as an external function if the logic is long
- Custom Formik components must be created in
src/app/modules/componentsto be a shared component
Steps for creating a Form
Step 1: Define the Type of Form Values
// SimpleForm.tsx
import React from "react";
import { useFormik, FormikErrors } from "formik";
// ต้อง export type ออกมาเพื่อให้ component อื่นใช้ได้
export type SimpleFormValues = {
name: string;
email: string;
};
Step 2: Fetch initial data from the API (if any)
// ใช้ react-query ในการดึงข้อมูลจาก api
const { data: user } = useGetUser('1');
const initialValues: SimpleFormValues = {
name: '',
email: '',
};
Step 3: Create useFormik
const formik = useFormik<SimpleFormValues>({
// กำหนดค่าเริ่มต้น — ถ้าดึงจาก API ให้ใช้ user || initialValues
initialValues: user || initialValues,
// ต้องใส่เสมอเมื่อ initialValues มาจาก API
enableReinitialize: true,
// validate function
validate: (values) => {
const errors: FormikErrors<SimpleFormValues> = {};
if (!values.name) {
errors.name = 'กรุณากรอกชื่อ';
}
return errors;
},
onSubmit: (values) => {
// ส่งข้อมูล
console.log(values);
},
});
Step 4: Render Form
return (
<div>
<Typography>ชื่อ</Typography>
<FormikTextField name="name" formik={formik} fullWidth />
<Typography>Email</Typography>
<FormikTextField name="email" formik={formik} fullWidth />
<Button onClick={() => formik.handleSubmit()}>บันทึก</Button>
<Button onClick={() => formik.handleReset()}>ยกเลิก</Button>
</div>
);
Example of a separated Validate Function
When the validate logic is long or complex, separate it into an external function:
const validate = (values: SimpleFormValues) => {
const errors: FormikErrors<SimpleFormValues> = {};
if (!values.name) {
errors.name = 'กรุณากรอกชื่อ';
} else if (values.name.length > 15) {
errors.name = 'ชื่อต้องไม่เกิน 15 ตัวอักษร';
}
if (!values.email) {
errors.email = 'กรุณากรอก Email';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) {
errors.email = 'รูปแบบ Email ไม่ถูกต้อง';
}
return errors;
};
Customizing MUI Components to work with Formik
If the MUI Component in the Template does not meet the need, create a custom component as follows:
Step 1: Create in the shared folder
Create a new file at src/app/modules/components/
Step 2: Define the Props to accept
The Component must accept the following props:
name: string— field name in Formikformik: FormikProps<T>— formik variable from useFormik
Step 3: Receive and send values using the Formik API
import { FormikProps } from 'formik';
import { TextField } from '@mui/material';
type CustomTextFieldProps = {
name: string;
formik: FormikProps<any>;
label?: string;
fullWidth?: boolean;
};
const CustomTextField = ({ name, formik, label, fullWidth }: CustomTextFieldProps) => {
// รับค่าด้วย getFieldMeta และ getFieldProps
const fieldProps = formik.getFieldProps(name);
const { touched, error } = formik.getFieldMeta(name);
return (
<TextField
{...fieldProps}
// ต้องตั้ง name และ id เพื่อให้ Robot ค้นหาได้
name={name}
id={name}
label={label}
fullWidth={fullWidth}
error={touched && Boolean(error)}
helperText={touched && error}
onChange={(e) => formik.setFieldValue(name, e.target.value)}
/>
);
};
MUI Components ready to use in the Template
See the customized Components at src/app/modules/_common/components/CustomFormik:
FormikTextField— text inputProvinceSelect— province selectEmployeeAutoComplete— employee autocompleteInterestCheckboxGroup— checkbox groupGenderRadioGroup— radio group
See more examples at the Demo Pos of SSD