Search code examples
reactjsperformancereact-hookssetstatereact-state-management

React Hooks: skip re-render on multiple consecutive setState calls


Suppose I have the following code: (which is too verbose)

function usePolicyFormRequirements(policy) {
  const [addresses, setAddresses] = React.useState([]);
  const [pools, setPools] = React.useState([]);
  const [schedules, setSchedules] = React.useState([]);
  const [services, setServices] = React.useState([]);
  const [tunnels, setTunnels] = React.useState([]);
  const [zones, setZones] = React.useState([]);
  const [groups, setGroups] = React.useState([]);
  const [advancedServices, setAdvancedServices] = React.useState([]);
  const [profiles, setProfiles] = React.useState([]);

  React.useEffect(() => {
    policiesService
      .getPolicyFormRequirements(policy)
      .then(
        ({
          addresses,
          pools,
          schedules,
          services,
          tunnels,
          zones,
          groups,
          advancedServices,
          profiles,
        }) => {
          setAddresses(addresses);
          setPools(pools);
          setSchedules(schedules);
          setServices(services);
          setTunnels(tunnels);
          setZones(zones);
          setGroups(groups);
          setAdvancedServices(advancedServices);
          setProfiles(profiles);
        }
      );
  }, [policy]);

  return {
    addresses,
    pools,
    schedules,
    services,
    tunnels,
    zones,
    groups,
    advancedServices,
    profiles,
  };
}

When I use this custom Hook inside of my function component, after getPolicyFormRequirements resolves, my function component re-renders 9 times (the count of all entities that I call setState on)

I know the solution to this particular use case would be to aggregate them into one state and call setState on it once, but as I remember (correct me, if I'm wrong) on event handlers (e.g. onClick) if you call multiple consecutive setStates, only one re-render occurs after event handler finishes executing.

Isn't there any way I could tell React, or React would know itself, that, after this setState another setState is coming along, so skip re-render until you find a second to breath.

I'm not looking for performance-optimization tips, I'm looking to know the answer to the above (Bold) question!

Or do you think I am thinking wrong?

Thanks!

--------------


UPDATE How I checked my component rendered 9 times?

export default function PolicyForm({ onSubmit, policy }) {
  const [formState, setFormState, formIsValid] = usePgForm();
  const {
    addresses,
    pools,
    schedules,
    services,
    tunnels,
    zones,
    groups,
    advancedServices,
    profiles,
    actions,
    rejects,
    differentiatedServices,
    packetTypes,
  } = usePolicyFormRequirements(policy);

  console.log(' --- re-rendering'); // count of this
  return <></>;
}

Solution

  • I thought I'd post this answer here since it hasn't already been mentioned.

    There is a way to force the batching of state updates. See this article for an explanation. Below is a fully functional component that only renders once, regardless of whether the setValues function is async or not.

    import React, { useState, useEffect} from 'react'
    import {unstable_batchedUpdates} from 'react-dom'
    
    export default function SingleRender() {
    
        const [A, setA] = useState(0)
        const [B, setB] = useState(0)
        const [C, setC] = useState(0)
    
        const setValues = () => {
            unstable_batchedUpdates(() => {
                setA(5)
                setB(6)
                setC(7)
            })
        }
    
        useEffect(() => {
            setValues()
        }, [])
    
        return (
            <div>
                <h2>{A}</h2>
                <h2>{B}</h2>
                <h2>{C}</h2>
            </div>
        )
    }
    

    While the name "unstable" might be concerning, the React team has previously recommended the use of this API where appropriate, and I have found it very useful to cut down on the number of renders without clogging up my code.