How to prevent reRender of React const which uses useState

Viewed 125

I am trying to prevent re rendering in a React functional component, but not sure how or what to implement, to achieve that. Reading up suggests using memo, but I don't understand how to implement it here.

The props include 'selectAll' (propType=boolean ), onSelected is a callback function and I use a stateHook for selected.

So, if the 'setSelected(newSelects)' method is called, it goes into an endless reRender loop; I have a work around in the return of the markup, which works, but I would prefer to use and learn and understand managing React's state.

Code:

function OptimiserTableBody(props) {
    const {Id, columns, send, onSelected, selectAll} = props;
    const [selected, setSelected] = useState([]);

    // Retrieve the data 
    const resOData = useQuery(o_data_query, {
    variables: {
        id: id,
        use: use
    },
    fetchPolicy: 'cache-and-network'
});

// Create an array from the retrieved data for the table, this is used to create the table rows
let steps = resOData.data != undefined ?  resOData.data.jobO.data : [];

 useEffect(() => {
    if (selectAll === true) {
        let newSelects = steps.map((n) => n.workStepId);
        setSelected(newSelects);
    }
}, [selectAll])


 return (
     <TableBody>
         {steps.map((step, index) => (
             let isItemSelected = isSelected(step.workStepId);
             
             // Workaround
             if (selectAll === true)
             {
                 isItemSelected = true;
             }
             return (
                 <TableRow ... >

                    <TableCell  key={'select'}  padding="checkbox"  >
                        <Checkbox 
                            checked={isItemSelected}
                             inputProps={{ 'aria-labelledby': step.workStepId }}
                                            style={{color: kbaColors.grey1}}
                                            onClick={(event) => handleClick(event, step.workStepId)}
                        />
                    </TableCell>
               </TableRow>
         ))}
      </TableBody>
    );
}
export default React.memo(OptimiserTableBody);

Thanks

1 Answers

Since you setSelected on every render, the component re-renders as you said, and goes into an infinite loop. I believe you want to update selected based on selectAll. You can do this inside a useEffect:

import {useEffect} from 'react'

useEffect(() => {
  if(selectAll){
    let newSelects = steps.map((n) => n.workStepId);
    setSelected(newSelects);
  }
}, [selectAll, steps])

By giving selectAll to the dependency array, you make sure that this useEffect runs only if selectAll changes.

Update

I noticed that you need to use steps as a state variable. So you can have two seperate useEffects:

 const [selected, setSelected] = useState([]);
 const [steps, setSteps] = useState([]);
 useEffect(() => {
    const resOData = useQuery(o_data_query, {
    variables: {
        id: id,
        use: use
    },
    fetchPolicy: 'cache-and-network'
    setSteps(resOData.data != undefined ? resOData.data.jobO.data : [])
 }, [] )   

Empty dependency array makes sure the useEffect will run only once, when the component mounts. Add this useEffect before the other one, and you should be good to go

Related