Skip to content

hook.js:608 Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render. Error Component Stack #330

Description

@ajie123100

import React from 'react'
import {
EffectComposer,
Outline,
Selection,
Select,
} from '@react-three/postprocessing'
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
const App = () => {
return (
<Canvas
gl={{ antialias: true }} // 启用抗锯齿
onCreated={({ gl }) => {
gl.setClearColor('rgb(50, 50, 50)')
}}
>
<OrbitControls
enablePan={true} // 允许平移
enableZoom={true} // 允许缩放
enableRotate={true} // 允许旋转
minDistance={5} // 最小缩放距离
maxDistance={20} // 最大缩放距离
minPolarAngle={Math.PI / 4}
maxPolarAngle={Math.PI / 2}
/>

<directionalLight color='#fff' intensity={2} position={[0, 0, 5]} />

  <Selection enabled>
    <EffectComposer autoClear={false}>
      <Outline blur edgeStrength={10} />
    </EffectComposer>
    <Select enabled> // Why is there a problem with enabled here?
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color='#000' />
      </mesh>
    </Select>
  </Selection>
</Canvas>

)
}

export default App

Activity

  1. givensuman commented on Mar 27, 2025

    @givensuman

    Second, this error spams the console in my attempt at implementing it. Was curious so I made a local copy of the "Selective outlines" example project and it happens there too. It's not very noticeable in the example but it also causes severe performance issues in my app where there are more complex models.

  2. kengru commented on Apr 1, 2025

    @kengru

    This also happens to me

  3. thirstyfish commented on Apr 3, 2025

    @thirstyfish

    Same here, on latest version of all packages the Select API doesn't seem to work due to this error

  4. Arunakemi commented on Apr 10, 2025

    @Arunakemi

    Ah sheesh, been fighting with this problem for a whole day. I should have looked around the issues first. Yep it seems Select API is kinda bronken right now. I wanted to use an Outline Effect on hover, but having enabled={hoverVariable} is re-rendering my mesh everytime (resseting position and rotation etc) because its making an update loop

  5. Arunakemi commented on Apr 12, 2025

    @Arunakemi

    Ah sheesh, been fighting with this problem for a whole day. I should have looked around the issues first. Yep it seems Select API is kinda bronken right now. I wanted to use an Outline Effect on hover, but having enabled={hoverVariable} is re-rendering my mesh everytime (resseting position and rotation etc) because its making an update loop

    Ah I just updated my Nodejs version and upgraded all my node modules at it fixed my issue. Maybe try updating everything?

  6. stefvw93 commented on Apr 26, 2025

    @stefvw93

    Any update on this? Having the exact same issue; using <Select enabled>. Followed the example almost exactly.

    <Selection>
    	<EffectComposer autoClear={false}>
    		<Outline blur edgeStrength={100} />
    	</EffectComposer>
    	<Select enabled>
    		{/* some meshes here... */}
    	</Select>
    </Selection>
    "@react-three/drei": "^10.0.7",
    "@react-three/fiber": "^9.1.2",
    "@react-three/postprocessing": "^3.0.4",
    "three": "^0.172.0"
    
  7. samuelgoodell commented on May 1, 2025

    @samuelgoodell

    I'm also experiencing this problem.

  8. Suprhimp commented on May 3, 2025

    @Suprhimp

    me too.. please help..

  9. alexandre-jurisoft commented on Jul 8, 2025

    @alexandre-jurisoft

    same here, it's a real shame :(

  10. Bossieh commented on Jul 10, 2025

    @Bossieh

    Having the same issue

      "dependencies": {
        "@react-three/drei": "10.4.2",
        "@react-three/fiber": "9.2.0",
        "@react-three/postprocessing": "3.0.4",
        "react": "19.1.0",
        "react-dom": "19.1.0",
        "react-error-boundary": "6.0.0",
        "three": "0.178.0"
      }
  11. dragospaulpop commented on Jul 31, 2025

    @dragospaulpop

    From my understanding, this is what causes the infinite loop:

    1. Object Tracking and State Comparison (Infinite Loop Cause 1):
    • The useEffect hook iterates through all Object3D instances within the group to determine if a state change is needed (changed flag is set if api.selected.indexOf(o) === -1).
    • However, only Mesh objects are subsequently added to the api.selected state via the current array.
    • This discrepancy means that non-mesh objects within the group are checked against api.selected (which exclusively contains meshes). For any non-mesh object, api.selected.indexOf(o) will always be -1, causing the changed flag to be perpetually true if any non-mesh object exists in the group.
    • Consequently, api.select is called on every render, resulting in an uncontrolled infinite render loop.
    1. useEffect Cleanup Always Alters State (Infinite Loop Cause 2):
    • The cleanup function api.select((state) => state.filter((selected) => !current.includes(selected))) correctly attempts to remove the currently added meshes from the api.selected state when the component unmounts.
    • However, because the useEffect hook is re-run after each addition or removal, the cleanup function is guaranteed to be called, it then alters the state resulting in an infinite loop.

    This might not be optimal as I'm new to the ecosystem, but here's my implementation of the hook with proper functionality:

    useEffect(() => {
      // run triggered through initialization or state update
      if (api) {
        const toBeAdded: THREE.Object3D[] = [];
        const toBeRemoved: THREE.Object3D[] = [];
        const current: THREE.Object3D[] = [];
    
        group.current.traverse((o) => {
          if (o.type === "Mesh") {
            // keep a track of all meshes in the group, to be referenced in the cleanup function
            current.push(o);
    
            // check if the mesh is already selected
            const alreadySelected = api.selected.includes(o);
    
            // if the mesh is not selected and the selection is enabled, mark it for selection
            // if the mesh is selected and the selection is disabled, mark it for removal
            if (enabled && !alreadySelected) {
              toBeAdded.push(o);
            } else if (!enabled && alreadySelected) {
              toBeRemoved.push(o);
            }
          }
        });
    
        // add the meshes that are not selected and the selection is enabled
        // this will trigger a re-run of the useEffect hook
        if (toBeAdded.length > 0) {
          api.select((state) => {
            return [...state, ...toBeAdded];
          });
        }
    
        // remove the meshes that are selected and the selection is disabled
        // this will trigger a re-run of the useEffect hook
        if (toBeRemoved.length > 0) {
          api.select((state) => {
            return state.filter((o) => !toBeRemoved.includes(o));
          });
        }
    
        // if there's nothing to add or remove the useEffect hook will not be re-run and everything stops here
    
        // cleanup function runs before the body of the next useEffect hook re-run
        return () => {
          // the cleanup function only handles objects removed from the scene
          // if a mesh doesn't have a parent, it means it's not attached to a scene and we can remove it from the selection
          // so that we don't hog the memory with deleted objects
    
          const orphaned = current.filter((o) => o.parent === null);
    
          if (orphaned.length > 0) {
            api.select((state) => {
              return state.filter((o) => !orphaned.includes(o));
            });
          }
    
          return;
        };
      }
    }, [enabled, children, api]);

    Logic:

    A. for a selection action:

    • enabled changes to true which causes the useEffect hook to run
    • objects are added to the state, triggering a re-run of the useEffect hook
    • cleanup function runs, does nothing because there's no orphaned objects
    • useEffect hook re-runs and sees that there's nothing to add or remove
    • useEffect hook stops here

    B. for a un-selection action:

    • enabled changes to false which causes the useEffect hook to run
    • objects are removed from the state, triggering a re-run of the useEffect hook
    • cleanup function runs, does nothing because there's no orphaned objects
    • useEffect hook re-runs and sees that there's nothing to add or remove
    • useEffect hook stops here

    C. for an unmount action:

    • useEffect cleanup runs, all objects are orphaned, so we remove them from the state
    • useEffect hook is destroyed
  12. devloop01 commented on Aug 11, 2025

    @devloop01

    I fixed this issue by updating the dependency array inside the Select Component.

    export function Select({ enabled = false, children, ...props }: SelectApi) {
        const group = useRef<THREE.Group>(null!);
        const api = useContext(selectionContext);
        useEffect(() => {
          // ...
        }, [enabled]); // <--- remove children, api
        return (
        	<group ref={group} {...props}>
        		{children}
        	</group>
        );
    }
  13. dklwo commented on Sep 18, 2025

    @dklwo

    So the issue is react 19 surfaces an existing issue where Select is reactively consuming an api that it also pushes updates to

    Proposed Fix: create seperate api for select component and effect components to consume

    You can also copy and use these modified components in the mean time

    import {Api} from "@react-three/postprocessing/src/Selection";
    import React, {createContext, useContext, useEffect, useMemo, useRef, useState, Provider} from "react";
    import {selectionContext, SelectApi} from "@react-three/postprocessing"
    import * as THREE from "three";
    
    export const selectContext = /* @__PURE__ */ createContext<Api | null>(null)
    
    export function ModifiedSelect({ enabled = false, children, ...props }: SelectApi) {
      const group = useRef<THREE.Group>(null!)
      const api = useContext(selectContext)
      useEffect(() => {
        if (api && enabled) {
          let changed = false
          const current: THREE.Object3D[] = []
          group.current.traverse((o) => {
            if (o.type === 'Mesh') {
              current.push(o)
            }
            if (api.selected.indexOf(o) === -1) changed = true
          })
          if (changed) {
            api.select((state) => [...state, ...current])
            return () => {
              api.select((state) => state.filter((selected) => !current.includes(selected)))
            }
          }
        }
      }, [enabled, children, api]);
      return (
        <group ref={group} {...props}>
          {children}
        </group>
      )
    }
    
    export function ModifiedSelection(
      { children, enabled = true }: { enabled?: boolean; children: React.ReactNode }
    ) {
      const [selected, select] = useState<THREE.Object3D[]>([])
      const selectApiRef = useRef({ selected, select, enabled })
      const selectApi = selectApiRef.current
    
      selectApi.selected = selected
      selectApi.select = select
      selectApi.enabled = enabled
    
      const selectionApi = useMemo(() => ({ selected, select, enabled }), [selected, select, enabled])
    
      return (
        <selectContext.Provider value={selectApiRef.current}>
          <selectionContext.Provider value={selectionApi}>
            {children}
          </selectionContext.Provider>
        </selectContext.Provider>
      )
    }
  14. dklwo commented on Sep 18, 2025

    @dklwo

    PR: #342

  15. hanpham32 commented on Mar 14, 2026

    @hanpham32

    this issue still persists for me

  16. lgsf commented on Apr 30, 2026

    @lgsf

    Still happening for me as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions