Form component created
Usage
<Form
initialValues={{
title: '',
name: ''
}}
onSubmit={formSubmit}
>
<Form.Field
name="title"
>
<Input placeholder="Наименование" />
</Form.Field>
<Form.Field
name="name"
>
<Input placeholder="Еще поле" />
</Form.Field>
<Button>Сохранить</Button>
</Form>
Form Component
import { FormField } from './FormField';
import { FormProps, FormContextT } from './../../../types/form';
import './style.scss';
export const FormContext = React.createContext<FormContextT>({
formData: {},
handleFieldChange: () => {}
});
export const Form = (props: FormProps): JSX.Element => {
const { children, initialValues, className, onSubmit } = props;
const [ formData, setFormData] = useState(initialValues);
const handlerSubmit = (e: React.SyntheticEvent): void => {
e.preventDefault();
onSubmit();
}
const handleFieldChange = (name: string, value: string) => {
setFormData({
...formData,
[name]: value
});
}
const context:FormContextT = {
formData,
handleFieldChange
}
return (
<FormContext.Provider value={context}>
<form
onSubmit={handlerSubmit}
>
{children}
</form>
</FormContext.Provider>
)
}
Form.Field = FormField
FormField Component
import React, { useContext } from 'react';
import { FormContext } from './Form';
import { FormFieldProps } from './../../../types/form';
export const FormField = (props: FormFieldProps): JSX.Element => {
const { name, children } = props;
const formContext = useContext(FormContext);
const { formData, handleFieldChange } = formContext;
const fieldChange = (e: React.ChangeEvent<HTMLInputElement>) => {
handleFieldChange(name, e.target.value);
if('onChange' in children.props) {
children.props.onChange(e);
}
}
console.log('re-render: ' + name);
return (
React.cloneElement(children, {
name,
onChange: fieldChange,
value: formData[name]
})
);
}
Input Component
export const Input = (props: PropsTypes) => {
const { className, value, name, placeholder, disabled, onChange, type = 'text' } = props;
console.log('re-render-input: ' + name);
return (
<input
type={type}
name={name!}
value={value!}
placeholder={placeholder!}
disabled={disabled!}
onChange={onChange!}
/>
)
}
All form field values are written to the formData object in the format {field: value} in the Form component and passed through the React Context to the FormField. The problem is that when the field value is overwritten, the formData in the parent component is updated, and all child FormFields are re-rendered. Tell me how to make it so that only the child that has been changed is re-rendered.
Tried to use the UseMemo hook but couldn't apply it correctly.
there are a few things here
FormFieldis using the whole form data, so even if you found a way to re-render only the components whose data has changed they would still re-render - they need to get only their own value.if the parent component is re-rendered ALL its children are re-rendered. you can wrap them with
React.memobut memoization comes with a price.React looks at the context value as a single value, and compares it by reference. so
useMemootherwise the context value will change each time the component it's created in re-renders for any reason.React.memowon't help here - docsPossible solutions (assuming it's important that fields won't re-render):
don't use context, pass only the field value to the form fields and wrap the fields with
React.memo.create a small external store with the form values and expose only an API (
useValue(fieldId)andsetValue(fieldId, value)). here is a sandbox with this solution