You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bug Description
When implementing a custom shader effect called RetroEffect with the wrapEffect utility from @react-three/postprocessing, I encountered the following error:
The error occurred with the following code structure: Uncaught TypeError: Converting circular structure to JSON: --> starting at object with constructor 'Object' | property '_owner' -> object with constructor 'FiberNode' | property 'stateNode' -> object with constructor 'RetroEffect' --- property '_reactInternals' closes the circle
The error occurred specifically when trying to pass props to a component created via wrapEffect:
`
// Original problematic code
import { EffectComposer, wrapEffect } from "@react-three/postprocessing";
const RetroEffect = wrapEffect(
RetroEffectImpl
) as React.ForwardRefExoticComponent<React.RefAttributes>;
// Later in the render:
`
Tech Stack
React 19.0.0
TypeScript 5.2.2
Three.js 0.174.0
@react-three/fiber 9.1.0
@react-three/postprocessing 3.0.4
postprocessing 6.37.1
Vite 5.0.8
react-router-dom 6.30.0
tailwindcss 4.0.15
@tailwindcss/vite 4.0.15
Root Cause
The issue is caused by how wrapEffect creates components that can produce circular references when used with certain props. When React tries to validate or process props containing circular references, it attempts to serialize them using JSON.stringify(), which fails with the "Converting circular structure to JSON" error.
My Solution
Instead of using wrapEffect, custom effects should be exposed as primitives using the recommended pattern from the React Postprocessing documentation:
`
// Before - Problematic approach
const RetroEffect = wrapEffect(
RetroEffectImpl
) as React.ForwardRefExoticComponent<React.RefAttributes>;
// After - Fixed approach using primitives
const RetroEffect = forwardRef<RetroEffectImpl, {}>(function RetroEffect(_, ref) {
const effect = useMemo(() => new RetroEffectImpl(), []);
return ;
});
`
// Usage remains the same: <EffectComposer> <RetroEffect ref={effect} /> </EffectComposer>
The RetroEffectImpl class extends the Effect class from the postprocessing library: class RetroEffectImpl extends Effect { public uniforms: Map<string, THREE.Uniform<any>>; constructor() { const uniforms = new Map<string, THREE.Uniform<any>>([ ["colorNum", new THREE.Uniform(4.0)], ["pixelSize", new THREE.Uniform(2.0)], ]); super("RetroEffect", ditherFragmentShader, { uniforms }); this.uniforms = uniforms; } // Getters and setters for the uniforms // ... }
By using the primitive-based approach, we can still access and modify the effect's properties through the ref, but we avoid the circular reference issue that occurs with the wrapEffect utility.
Describe the issue
Bug Description
When implementing a custom shader effect called RetroEffect with the wrapEffect utility from @react-three/postprocessing, I encountered the following error:
The error occurred with the following code structure:
Uncaught TypeError: Converting circular structure to JSON: --> starting at object with constructor 'Object' | property '_owner' -> object with constructor 'FiberNode' | property 'stateNode' -> object with constructor 'RetroEffect' --- property '_reactInternals' closes the circle
The error occurred specifically when trying to pass props to a component created via wrapEffect:
`
// Original problematic code
import { EffectComposer, wrapEffect } from "@react-three/postprocessing";
const RetroEffect = wrapEffect(
RetroEffectImpl
) as React.ForwardRefExoticComponent<React.RefAttributes>;
// Later in the render:
`
Tech Stack
Root Cause
The issue is caused by how
wrapEffect
creates components that can produce circular references when used with certain props. When React tries to validate or process props containing circular references, it attempts to serialize them usingJSON.stringify()
, which fails with the "Converting circular structure to JSON" error.My Solution
Instead of using
wrapEffect
, custom effects should be exposed as primitives using the recommended pattern from the React Postprocessing documentation:`
// Before - Problematic approach
const RetroEffect = wrapEffect(
RetroEffectImpl
) as React.ForwardRefExoticComponent<React.RefAttributes>;
// After - Fixed approach using primitives
const RetroEffect = forwardRef<RetroEffectImpl, {}>(function RetroEffect(_, ref) {
const effect = useMemo(() => new RetroEffectImpl(), []);
return ;
});
`
// Usage remains the same: <EffectComposer> <RetroEffect ref={effect} /> </EffectComposer>
The RetroEffectImpl class extends the Effect class from the postprocessing library:
class RetroEffectImpl extends Effect { public uniforms: Map<string, THREE.Uniform<any>>; constructor() { const uniforms = new Map<string, THREE.Uniform<any>>([ ["colorNum", new THREE.Uniform(4.0)], ["pixelSize", new THREE.Uniform(2.0)], ]); super("RetroEffect", ditherFragmentShader, { uniforms }); this.uniforms = uniforms; } // Getters and setters for the uniforms // ... }
By using the primitive-based approach, we can still access and modify the effect's properties through the ref, but we avoid the circular reference issue that occurs with the wrapEffect utility.
References
React Postprocessing Custom Effects Documentation
https://react-postprocessing.docs.pmnd.rs/effects/custom-effects
Related issue with circular JSON errors in React: facebook/prop-types#383
https://github.com/facebook/prop-types/issues/383
Three.js discussion on handling circular references: Issue #18934
https://github.com/mrdoob/three.js/issues/18934
Reproduction Link
No response
Steps to reproduce
After setting environment:
copy / paste Dither tailwind + ts code into new .tsx component then use it in some page (wrapped in react-router-dom 6.30.0). Done.
Validations
The text was updated successfully, but these errors were encountered: