📜 ⬆️ ⬇️

Reusing forms on React

Hello!

We have an admin panel and many forms in the BCS, but in the React community there is no generally accepted method - how to design them for reuse. The official Facebook guide does not contain detailed information on how to work with forms in real conditions, where validation and reuse are needed. Someone use redux-form, formik, final-form, or even write their own solution.


In this article we will show one of the options for working with forms on React. Our stack will be like this: React + formik + Typescript. We will show:
')

With the new business task, we learned that we will need to make 15-20 similar forms, and hypothetically there may be even more of them. We had one dinosaur form on the config, which worked with data from the `store`, sent actions to save and execute requests through` sagas`. She was wonderful, doing business value. But it was already non-expandable and non-reusable, only with poor code and the addition of crutches.

The task is: to rewrite the form so that it can be reused an unlimited number of times. Well, we recall functional programming, it has pure functions that do not use external data, in our case `redux`, only what they are sent in arguments (props).

And that’s what happened.

The idea of ​​our component is that you create a wrapper (container) and write in it the logic of working with the outside world (receiving data from the Redux store and sending actions). For this, the container component must be able to receive some information through the callbacks. The whole list of form props:

interface IFormProps { //          IsSubmitting?: boolean; //     submitText?: string; //    resetText?: string; //       (  ) validateOnChange?: boolean; //     blur'e  (  ) validateOnBlur?: boolean; // ,      . config: IFieldsFormMetaModel[]; //  . fields: FormFields; //    validationSchema: Yup.MidexSchema; //     onSubmit?: () => void; //     reset  onReset?: (e: React.MouseEvent<HTMLElement>) => void; //    onChangeField?: ( e: React.SyntaticEvent<HTMLInputElement, name: string; value: string ) => void; //      +    onChangeFields?: (values: FormFields, prop: { isValid }) => void; } 

Using Formik


We use the <Formik /> component.

 render() { const { fields, validationSchema, validateOnBlur = true, validateOnChange = true, } = this.props; return ( <Formik initialValues={fields} render={this.renderForm} onSubmit={this.handleSubmitForm} validationSchema={validationSchema} validateOnBlur={validateOnBlur} validateOnChange={validateOnChange} validate={this.validateFormLevel} /> ); } 

In the prop'e of the `validate` form, we call the` this.validateFormLevel` method, in which we give the container component the opportunity to get all the changed fields and check if they are valid.

 private validateFormLevel = (values: FormFields) => { const { onChangeFields, validationSchema } = this.props; if (onChangeFields) { validationSchema .validate(values) .then(() => { onChangeFields(values, { isValid: true }); }) .catch(() => { onChangeFields(values, { isValid: false }); }); } } 

Here you have to call validation again in order to make it clear to the container whether the fields are valid. When submitting a form, we simply call prop `onSubmit`:

 private handleSubmitForm = (): void => { const { onSubmit } = this.props; if (onSubmit) { onSubmit(); } } 

With props 1-5 everything should be clear. Let's move on to 'config', 'fields' and 'validationSchema'.

Props 'config'


 interface IFieldsFormMetaModel { /**   */ sectionName?: string; sectionDescription?: string; fieldsForm?: Array<{ /**    */ name?: string; //          prop 'fields' /**    checked */ checked?: boolean; /** enum,      */ type?: ElementTypes; /**    */ label?: string; /**    */ helperText?: string; /**      */ required?: boolean; /**      */ disabled?: boolean; /**  -    */ minLength?: number; /**            */ initialValue?: IInitialValue; /**      */ selectItems?: ISelectItems[]; //   select, dropdown   }>; } 

Based on this interface, we create an array of objects and render “section” -> “section fields” according to this scheme. So we can show several fields for the section or in each one at a time, if you need a title and a note. How the rendering works, we will show a little later.
A short example of a config:

 export const config: IFieldsFormMetaModel[] = [ { sectionName: ' ', fieldsForm: [{ name: 'subject', label: '', type: ElementTypes.Text, }], }, { sectionName: '', sectionDescription: '  ', fieldsForm: [{ name: 'reminder', disabled: true, label: '', type: ElementTypes.CheckBox, checked: true, }], }, ]; 

Based on business data, the values ​​for the `name` keys are set. The same values ​​are used in the prop `fields` keys to transmit the original or changed values ​​for the formic.

For the example above, `fields` might look like this:

 const fields: SomeBusinessApiFields = { subject: '  ', reminder: 'yes', } 

For validation, we need to pass Yup Schema. We give the form to the form with the container props, describing there interactions with external data, for example, requests.

The form cannot affect the scheme in any way, for example:

 export const CreateClientSchema: ( props: CreateClientProps, ) => Yup.MixedSchema = (props: CreateClientProps) => Yup.object( { subject: Yup.string(), description: Yup.string(), date: dateSchema, address: addressSchema(props), }, ); 

Render and field optimization


For rendering, we made a map for quick search by key. It looks concise and the search is faster than by `switch`.

 fieldsMap: Record< ElementTypes, ( state: FormikFieldState, handlers: FormikHandlersState, field: IFieldsFormInfo, ) => JSX.Element > = { [ElementTypes.Text]: ( state: FormikFieldState, handlers: FormikHandlersState, field: IFieldsFormInfo ) => { const { values, errors, touched } = state; return ( <FormTextField key={field.name} element={field} handleChange={this.handleChangeField(handlers.setFieldValue, field.name)} handleBlur={handlers.handleBlur} value={values[field.name]} error={touched[field.name] && errors[field.name] || ''} /> ); }, [ElementTypes.TextSearch]: (...) => {...}, [ElementTypes.TextArea]: (...) => {...}, [ElementTypes.Date]: (...) => {...}, [ElementTypes.CheckBox]: (...) => {...}, [ElementTypes.RadioButton]: (...) => {...}, [ElementTypes.Select]: (...) => {...}, }; 

Each component field is stateful. It is in a separate file and is wrapped in `React.memo`. All values ​​are transmitted through props, bypassing the `children`, to avoid unnecessary renderer.

Conclusion


Our form is not ideal, for each case we have to create a container wrapper for working with data. Save them in the `store`, convert and make requests. There is a repetition of code that you want to get rid of. We are trying to find a new solution in which the form, depending on the props, will take the desired key from the store with fields, actions, diagrams and config. In one of the following posts we will tell you what came of it.

Source: https://habr.com/ru/post/461919/


All Articles