2

I am implementing a form which is generated using a Json. The Json is retrieved from API and then looping over the items I render the input elements. Here is the sample Json :

{
  name: {
      elementType: 'input',
      label: 'Name',
      elementConfig: {
        type: 'text',
        placeholder: 'Enter name'
      },
      value: '',
      validation: {
        required: true
      },
      valid: false,
      touched: false
    }
   }

Here is how I render the form :

    render() {
        const formElementsArray = [];
        for (const key in this.props.deviceConfig.sensorForm) {
         formElementsArray.push({
            id: key,
            config: this.props.deviceConfig.sensorForm[key]
         });

    const itemPerRow = 4;
      const rows = [
        ...Array(Math.ceil(props.formElementsArray.length / itemPerRow))
      ];
      const formElementRows = rows.map((row, idx) =>
        props.formElementsArray.slice(
          idx * itemPerRow,
          idx * itemPerRow + itemPerRow
        )
      );
      const content = formElementRows.map((row, idx) => (
        <div className='row' key={idx}>
          {row.map((formElement) => (
            <div className='col-md-3' key={formElement.id}>
              <Input
                key={formElement.id}
                elementType={formElement.config.elementType}
                elementConfig={formElement.config.elementConfig}
                value={formElement.config.value}
                invalid={!formElement.config.valid}
                shouldValidate={formElement.config.validation}
                touched={formElement.config.touched}
                label={formElement.config.label}
                handleChange={(event) => props.changed(event, formElement.id)}
              />
            </div>
          ))}
        </div>
 ...
    }

I am storing the form state in redux and on every input change , I update the state. Now the problem is everytime I update the state, the entire form is re-rendered again... Is there any way to optimise it in such a way that only the form element which got updated is re-rendered ?

Edit :

  1. I have used React.memo in Input.js as : export default React.memo(input);

  2. My stateful Component is Pure component.

  3. The Parent is class component.

Edit 2 :

Here is how I create formElementArray :

const formElementsArray = [];
for (const key in this.props.deviceConfig.sensorForm) {
    formElementsArray.push({
    id: key,
    config: this.props.deviceConfig.sensorForm[key]
});
3
  • I don't see an id in in your json - is there one? Commented May 22, 2021 at 20:18
  • @WillJenkins the id is key name i.e name in the Json Commented May 22, 2021 at 20:20
  • how is name mapped to id? it isn't shown in your question Commented May 22, 2021 at 20:23

3 Answers 3

1

You can make content as a separate component like this. And remove formElementsArray prop from parent component.

export default function Content() {
  const formElementRows = useForElementRows();
      formElementRows.map((row, idx) => (
            <Input
              formId={formElement.id}
              handleChange={props.changed}
            />
      )
}

Inside Input.js

const handleInputChange = useCallback((event) => {
   handleChange(event, formId);
}, [formId, handleChange]);
<input handleChange={handleInputChange} />
export default React.memo(Input)

So you can memoize handleChange effectively. And it will allow us to prevent other <Input /> 's unnecessary renders. By doing this forElementRows change will not cause any rerender for other components.

Sign up to request clarification or add additional context in comments.

7 Comments

once Input changes , the form state is updated. Wouldn't this cause Content to re-render? and as a result all Input re-renders ??
Only Content will rerender bcs it's state chagned, but not for other Inputs since their props didn't change.
And yes Input should be pureComponent. Which is needed to skip rerender when it's props didn't change. In general Input is purecomponent naturally.
I have updated answer since you need to memorize handlechange. If Input is not Purecomponent, you can just wrap it with React.memo(Input) on it's export
Main issue was handlechange, since it's created everytime we rerender Content.
|
1

You could try a container, as TianYu stated; you are passing a new reference as change handler and that causes not only the component to re create jsx but also causes virtual DOM compare to fail and React will re render all inputs.

You can create a container for Input that is a pure component:

const InputContainer = React.memo(function InputContainer({
  id,
  elementType,
  elementConfig,
  value,
  invalid,
  shouldValidate,
  touched,
  label,
  changed,
}) {
  //create handler only on mount or when changed or id changes
  const handleChange = React.useCallback(
    (event) => changed(event, id),
    [changed, id]
  );
  return (
    <Input
      elementType={elementType}
      elementConfig={elementConfig}
      value={value}
      invalid={invalid}
      shouldValidate={shouldValidate}
      touched={touched}
      label={label}
      handleChange={handleChange}
    />
  );
});

Render your InputContainer components:

{row.map((formElement) => (
  <div className="col-md-3" key={formElement.id}>
    <InputContainer
      key={formElement.id}
      elementType={formElement.config.elementType}
      elementConfig={formElement.config.elementConfig}
      value={formElement.config.value}
      invalid={!formElement.config.valid}
      shouldValidate={formElement.config.validation}
      touched={formElement.config.touched}
      label={formElement.config.label}
      //re rendering depends on the parent if it re creates
      //  changed or not
      changed={props.changed}
    />
  </div>
))}

1 Comment

Yeah.. I got the mistake I did
0

You have to follow some steps to stop re-rendering. To do that we have to use useMemo() hook.

First Inside Input.jsx memoize this component like the following.

export default React.memo(Input);

Then inside Content.jsx, memoize the value of elementConfig, shouldValidate, handleChange props. Because values of these props are object type (non-primitive/reference type). That's why every time you are passing these props, they are not equal to the value previously passed to that prop even their value is the same (memory location different).

const elementConfig = useMemo(() => formElement.config.elementConfig, [formElement]);
const shouldValidate = useMemo(() => formElement.config.validation, [formElement]);
const handleChange = useCallback((event) => props.changed(event, formElement.id), [formElement]);

return <..>
  <Input
    elementConfig={elementConfig }
    shouldValidate={elementConfig}
    handleChange={handleChange}
  />
<../>

As per my knowledge, this should work. Let me know whether it helps or not. Thanks, brother.

3 Comments

Thanks, @gmoniava. Sorry for that mistake. Edited my answer.
how elementConfig will get formElement obbject ??
@programoholic Ahh... It's a problem. Outside the loop, it won't get this variable. Then you can try wrapping the full loop in useMemo.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.