React update child component when non-state value in parent changes

I’m working on the product pages for an ecommerce React app. The products may have several options that the customer may need to fill in. When complete, the customer will click add to cart and the form will submit.

For validation purposes, the form on the product page needs to know which options are required and filled in. I don’t want to use state here, because then the entire form will re-render every time the customer makes a choice, causing noticeable lag. Even something as simple as typing text into a textfield will trigger a re-render on every keystroke. Instead, the options are in child components with their own state, and re-rendering just the little option component does not cause the page to lag.

So now I’m trying to use useRef in the parent component to store the options’ required/validity status. The parent has a receiver function that receives the option value from the child, and this is passed down to the child. When the child option changes, it calls the receiver function, and the parent receives the data and stores it in a ref array, so each option has an entry in the array.

This all works fine. The parent knows the option values, and which are valid / invalid, etc, so when the user attempts to submit the form, the parent can tell them if they missed something.

The problem is that I want to highlight the invalid options. This means the child that renders the option needs to know if the option is valid or not. But updating the ref in the parent doesn’t trigger a re-render of the child. I knew this would happen but I thought I could figure a way around it. So far, no luck 🙁

So, is there a better way to do this, other than useState and useRef? I’m still somewhat new to React so I’m sure there are a lot of advanced hooks etc that I still need to learn.

Note that I did, for argument’s sake, switch the ref to a piece of state. It worked and the child options re-rendered, but there was horrifying lag when the customer typed into text fields, so that’s out.

Anyway, here’s some of the relevant code from the parent:


let ref_optionValidity = useRef([]);

let adjustOptionValidity = useCallback((options,code="",newValue="") => {
   let optionValidities = [...ref_optionValidity.current];

   options.forEach((option,rowIndex)=>{
      let value = optionValidities[rowIndex] ? optionValidities[rowIndex].value : option.value || "";
      if ( option.code === code ) {
         value = newValue;
      }
      let isValid = option.required && !value ? false : true;
      if ( !optionValidities[rowIndex] ) {
         optionValidities.push({
            isValid:isValid,
            value:value,
            code:option.code
         });
      } else {
         optionValidities[rowIndex].value = value;
         optionValidities[rowIndex].isValid = isValid;
      }
   });
   ref_optionValidity.current = [...optionValidities];
},[]);

return (
   <Options
      ...
      optionValidity={ref_optionValidity}
      adjustOptionValidity={adjustOptionValidity}
   />
);

On the child I just have a simple useEffect to test if the child will pick up the change to the ref, and as expected, it doesn’t.

useEffect(()=>{
   console.log("useEffect running");
},[
   optionValidity.current[rowIndex].isValid
]);