React.JS Component re-rendering and losing focus

42 Views Asked by At

So the BetterInputGroup component is re-rendering after ever letter typed, I'm still struggling to wrap my head around how React works like that and why. Is it because I'm using the BetterInputGroup component inside the AddTask component? If I move it out or to another file I lose access to the formData state and handleFormChange.

Is there a better way of doing this or am I being really dumb?

const AddTask = () => {

  const [formData, setFormData] = useState({
    data: {taskname: "", department: "", frequency: -1, subtasks: [{name: "", description: "", inputtype: 0, inputprefix: ""}]},
    validity: {taskname: false, department: false, frequency: false, subtasks: [{name: false, inputtype: false}]}})

    const BetterInputGroup = ({sidetext, styledata, pathtodata}) => {
      const pathList = pathtodata.split(".")
      let compValid = pathList.length > 1 ? formData["validity"][pathList[0]][pathList[1]] : formData["validity"][pathtodata]
      let compValue = pathList.length > 1 ? formData["data"][pathList[0]][pathList[1]] : formData["data"][pathtodata]
      return (
        <CInputGroup className={styledata}>
          <CInputGroupText>{sidetext}</CInputGroupText>
          <CFormInput type="text" value={compValue}
            onChange={e => {handleFormChange(e, pathtodata)}}
            valid={compValid}
            invalid={!compValid}/>
        </CInputGroup>
      )
    }
  
  const handleFormChange = (event, path) => {
    let newFormData = {...formData}
    const pathList = path.split(".")
    if(pathList === "subtasks") {
      newFormData["data"]["subtasks"][pathList[1]][pathList[2]] = event.target.value
      if(newFormData["validity"]["subtasks"][pathList[1]][pathList[2]] != null) {
  //        newFormData["validity"]["subtasks"][pathList[1]][pathList[2]] = regexes["subtasks"][pathList[2]].test(event.target.value)
      }
    } else {
      newFormData["data"][pathList[0]] = event.target.value
      newFormData["validity"][pathList[0]] = regexes[pathList[0]].test(event.target.value)
    }
    setFormData(newFormData)
  }
  
  const regexes = {taskname: /[a-zA-Z\s]+$/, department: /[1-9]$/, frequency: /^(?:[1-9]\d?|[12]\d{2}|3[0-5]\d|36[0-5])$/, sutasks: {name: /[a-zA-Z\s]{20}$/, inputtype: /[1-9]$/}}

  return (
      <>
          <BetterInputGroup styledata="w-auto p-3 has-validation" sidetext="Task name" pathtodata="taskname" />

      </>
  )
1

There are 1 best solutions below

0
s4n.ty On

Yes, the reason why BetterInputGroup is re-rendering after every letter typed is because it is defined inside the AddTask component and is using formData and handleFormChange from the parent component's state. In React, when a state or prop changes, all child components that depend on that state or prop will re-render.

One solution to reduce the unnecessary re-renders of BetterInputGroup is to extract it as a separate component and pass the necessary props down to it. For example:

// Extracted component
const InputGroup = ({sidetext, styledata, value, onChange, valid, invalid}) => {
  return (
    <CInputGroup className={styledata}>
      <CInputGroupText>{sidetext}</CInputGroupText>
      <CFormInput
        type="text"
        value={value}
        onChange={onChange}
        valid={valid}
        invalid={invalid}
      />
    </CInputGroup>
  );
};

// Usage in AddTask component
const AddTask = () => {
  const [formData, setFormData] = useState({
    data: {
      taskname: "",
      department: "",
      frequency: -1,
      subtasks: [{name: "", description: "", inputtype: 0, inputprefix: ""}],
    },
    validity: {
      taskname: false,
      department: false,
      frequency: false,
      subtasks: [{name: false, inputtype: false}],
    },
  });

  const handleFormChange = (event, path) => {
    let newFormData = {...formData};
    const pathList = path.split(".");
    if (pathList === "subtasks") {
      newFormData["data"]["subtasks"][pathList[1]][pathList[2]] = event.target.value;
      if (newFormData["validity"]["subtasks"][pathList[1]][pathList[2]] != null) {
        // newFormData["validity"]["subtasks"][pathList[1]][pathList[2]] = regexes["subtasks"][pathList[2]].test(event.target.value)
      }
    } else {
      newFormData["data"][pathList[0]] = event.target.value;
      newFormData["validity"][pathList[0]] = regexes[pathList[0]].test(event.target.value);
    }
    setFormData(newFormData);
  };

  const regexes = {
    taskname: /[a-zA-Z\s]+$/,
    department: /[1-9]$/,
    frequency: /^(?:[1-9]\d?|[12]\d{2}|3[0-5]\d|36[0-5])$/,
    sutasks: {name: /[a-zA-Z\s]{20}$/, inputtype: /[1-9]$/},
  };

  return (
    <>
      <InputGroup
        styledata="w-auto p-3 has-validation"
        sidetext="Task name"
        value={formData.data.taskname}
        onChange={(e) => handleFormChange(e, "taskname")}
        valid={formData.validity.taskname}
        invalid={!formData.validity.taskname}
      />
    </>
  );
};

By extracting the InputGroup component, we can pass only the necessary props instead of passing the whole state object and function down as a prop. This can help reduce unnecessary re-renders of the component.