React报错:Rendered fewer hooks than expected问题排查求助
排查React组件Hook报错:Rendered fewer hooks than expected
在项目中使用ScrollRenderingCom函数组件时,已严格遵循React Hook规则:未在循环、条件或嵌套函数内调用Hook,所有Hook均放置在return语句之前,但仍出现报错:
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement
ScrollRenderingCom组件代码
function ScrollRenderingCom(props) { const { children, aproximateHeight = 200} = props; const [isIntersecting, setIsIntersecting] = useState(false); const fakeComponent = useRef(null); const callback = useCallback((entries) => { const [entry] = entries; if (entry.isIntersecting) { setIsIntersecting(true) } }, []); useEffect(() => { const options = { root: null, rootMargin: '0px', threshold: 0, } const observer = new IntersectionObserver(callback, options); observer.observe(fakeComponent.current); return () => { observer.disconnect(); } }, [callback]); return ( isIntersecting ? children : <div ref={fakeComponent} className={children.type().props.className} style={{ visibility: 'hidden', minHeight: aproximateHeight + 'px' }} > </div> ) }
报错栈追踪
at ScrollRenderingCom (http://localhost:3000/static/js/bundle.js:3468:5) at div at PostSection1 at Home at Routes (http://localhost:3000/static/js/bundle.js:229775:5) at div at Router (http://localhost:3000/static/js/bundle.js:229708:15) at BrowserRouter (http://localhost:3000/static/js/bundle.js:228517:5) at App
Child组件代码
function Child() { const [successMessage, setSuccessMessage] = useState(false); useEffect(() => { if (successMessage) { alert('this app works correctly') } return () => {setSuccessMessage(false)} }) const handleOnClick = () => { setSuccessMessage(true); } return( <div className="child" style={{width: '500px', height: '200px', backgroundColor: 'green', color: 'white' }} > <h1>this component should be rendered when the empty blue div element completely enters the viewport </h1> <h3>if you see the above message click Yes</h3> <button onClick={handleOnClick} >Yes</button> </div> ) }
问题原因
报错的核心是代码中的children.type()调用:
children.type()会直接执行Child组件的函数,这意味着在ScrollRenderingCom的渲染过程中,会触发Child组件内部的Hook(useState、useEffect)调用。- 当
isIntersecting为false时,ScrollRenderingCom不渲染children,但此时已经调用了Child的Hook;当isIntersecting变为true时,渲染children会再次调用Child的Hook,导致两次渲染周期中Hook调用数量不一致,触发React的Hook校验报错。
解决方案
不要通过调用组件函数的方式获取props,直接从children的props属性中读取即可(适用于children为单个React元素的场景):
修改ScrollRenderingCom中的className赋值:
className={children.props.className}
修复后的完整ScrollRenderingCom组件代码:
function ScrollRenderingCom(props) { const { children, aproximateHeight = 200} = props; const [isIntersecting, setIsIntersecting] = useState(false); const fakeComponent = useRef(null); const callback = useCallback((entries) => { const [entry] = entries; if (entry.isIntersecting) { setIsIntersecting(true) } }, []); useEffect(() => { const options = { root: null, rootMargin: '0px', threshold: 0, } const observer = new IntersectionObserver(callback, options); observer.observe(fakeComponent.current); return () => { observer.disconnect(); } }, [callback]); return ( isIntersecting ? children : <div ref={fakeComponent} className={children.props.className} style={{ visibility: 'hidden', minHeight: aproximateHeight + 'px' }} > </div> ) }
另外,Child组件中的useEffect缺少依赖数组,建议补充完整,避免不必要的重复执行:
useEffect(() => { if (successMessage) { alert('this app works correctly') } return () => {setSuccessMessage(false)} }, [successMessage])
内容的提问来源于stack exchange,提问作者Mohamad Bakhtiari
相关产品推荐
相关产品推荐

