Why so many rerenders?

Hi,

I’m building a Configurator in R3F, with alot of options to customize. Currently i’m using a custom hook to share the states across my Components (Custumization.jsx, I cut some code for readability) :

export const CustomizationProvider = (props) => {
    const [OFL_TB, setOFL_TB] = useState("CPL quer Vivo Choco"); // material bandseitig
    const [OFL_TB_BGS, setOFL_TB_BGS] = useState("CPL quer Vivo Choco"); // material bandgegenseite
    const [OFL_ZA, setOFL_ZA] = useState("CPL quer Vivo Choco"); //zarge material
    [...]

    return (
      <CustomizationContext.Provider 
      value={{
          OFL_TB,
          setOFL_TB,
          OFL_TB_BGS,
          setOFL_TB_BGS,
          OFL_ZA,
          setOFL_ZA,
          [...]
      }}
      >
        {props.children}
      </CustomizationContext.Provider>
    );
  };
  
  export const useCustomization = () => {
    const context = useContext(CustomizationContext);
    return context;
  };

I’m wrapping my App.jsx in this cutom hook, so i get access to all states from everywhere. When I’m logging “rerender” in one of the components of my Experience.jsx (it’s in the App.jsx) and i refresh the page i get 13 logs (Image below). I guess the problem is the custom hook but how can I get access to the states in a more efficient way?
Every tip would be helpful. Thanks

Zustand may be what you’re looking for - it’s a bit more flexible and granular than default React contexts.

re-render in react isn’t harmful per-se, all it does is check for updates. but the reason is, like @mjurczyk said, context. it isn’t really meant to hold app state, it re-renders all context listeners and their sub trees on every change, which isn’t optimal.

worse, every time CustomizationProvider executes it triggers a fresh re-render of everything listening to CustomizationContext because you didn’t memoize the context value, which i would consider a bug. context shouldn’t be used w/o memoization.

   const value = useMemo(() => ({
     OFL_TB,
     setOFL_TB,
     OFL_TB_BGS,
     setOFL_TB_BGS,
     OFL_ZA,
     setOFL_ZA,
   }), [conditions])
   return (
     <CustomizationContext.Provider value={value}>

however, if his log is in a single component that is used once, and the context is changed once, should we not still get 1 log? I mean, of course, unless he actually calls all 13 of those setXXX methods

yep, there’s definitively something over triggering it. in strict dev mode the component tree has to render twice because react stresses it out on purpose to flush out bugs and race conditions.

Hello, thanks for the replies, i tried it with Zustand and the same happens so here is more code for explanation, thats the Licht Component:

export function Licht() {
  const { lichtZustand} = useCustomization();

  return (
    <>
      {/* SpotLight BGS */}
      <spotLight
        ref={spotLight}
        visible={!lichtZustand}
        position={[-2.5, 1.5, -1.5]}
        intensity={20}
        shadow-mapSize-width={4096}
        shadow-mapSize-height={4096}
        color={colorSpot}
        angle={angleSpot}
        distance={7}
        decay={decay}
        penumbra={penumbra}
        shadow-normalBias={0.02}
        target={targetObject}
        castShadow
      />

      <directionalLight
        shadow-mapSize-width={2048}
        shadow-mapSize-height={2048}
        position={[3, 4, 3]}
        ref={directionalLight}
        intensity={intensity}
        castShadow
        shadow-camera-far={10}
        shadow-normalBias={normalBias}
        color={color}
      />
    </>
  );
}

The Licht Component is in the Experience.jsx:

export const Experience = () => {
  const {
    sceneVisibility,
    wandFarbe,
    MOD_ZA,
    FALZ_MASS,
    BR_TB,
    HOE_TB,
    TB_STAERKE,
    ANR,
    LA,
    BR_ZFM,
    HOE_ZFM,
    FALZ_ZA_TIEF,
    TIEF_ZA,
    AFLG,
    BEKL_BR,
    KA_PROFIL,
    HOE_WAND,
    setLichtZustand,
    lichtZustand
  } = useCustomization();

  return (
    <>

      {/* PERFORMANCE MONITOR */}
      <Perf position="top-left" />

      {/* Kamera Kontrolle implementiert mit OrbitControls */}
      <CameraControls HOE_TB={HOE_TB} BR_TB={BR_TB}/>

      <Environment
        files={"./HDRI/cayley_interior_1k.hdr"}
        intensity={1}
        resolution={1024}
      />

      <Türelement
        HOE_TB={HOE_TB}
        BR_TB={BR_TB}
        TB_STAERKE={TB_STAERKE}
        KAUS={KA_PROFIL === "STUMPF" ? false : true}
        FALZ_MASS={FALZ_MASS}
        KA_PROFIL={KA_PROFIL}
        ANR={ANR}
        MOD_ZA={MOD_ZA}
        LA={LA}
        FALZ_ZA_TIEF={FALZ_ZA_TIEF}
        HOE_ZFM={HOE_ZFM}
        BR_ZFM={BR_ZFM}
        TIEF_ZA={TIEF_ZA}
        AFLG={AFLG}
        BEKL_BR={BEKL_BR}
      />

      <WandNeu 
        FALZ_MASS={FALZ_MASS}
        WAND_FA={wandFarbe}
        HOE_TB={HOE_TB}
        BR_TB={BR_TB}
        TIEF_ZA={TIEF_ZA}
        KAUS={KA_PROFIL === "STUMPF" ? false : true}
        HOE_WAND={HOE_WAND}
        setLichtZustand={setLichtZustand}
        lichtZustand={lichtZustand}
      />

      <Licht />

    </>
  );
};

Is the problem, that the Experience uses so many states? And the initializing of those states triggers one rerender of the Experience? If yes, how can i make sure this does not happen (using default values maybe)?

Zustand can pick state, the point of it is that components can listen and react only to state they use and everything else stays put. But if you use it the same way as context, one major glob and every change triggers it and then you distribute it via prop drilling, that wouldn’t make sense.

Using it is good, but move state access to the components that rely on state

function Foo() {
  const foo = useStore(state => state.foo)
  const bar = useStore(state => state.bar)

That component listens to foo and bar, will only render it either of these changes unless it gets triggered by a parent rendering.

In your app there’s a part that triggers a state update 13 times, so you end up on the parent component 13 times. You have to figure out which does that.

2 Likes