给定以下组件,当我按下年龄选择器并将值更改为15时,这样我就呈现了一个没有驾驶执照字段的表单,我会得到错误:
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
at invariant (react-dom.development.js:55)
at finishHooks (react-dom.development.js:11581)
at updateFunctionComponent (react-dom.development.js:14262)
at beginWork (react-dom.development.js:15103)
at performUnitOfWork (react-dom.development.js:17817)
at workLoop (react-dom.development.js:17857)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
at invokeGuardedCallback (react-dom.development.js:256)
at replayUnitOfWork (react-dom.development.js:17113)
at renderRoot (react-dom.development.js:17957)
at performWorkOnRoot (react-dom.development.js:18808)
at performWork (react-dom.development.js:18716)
at flushInteractiveUpdates$1 (react-dom.development.js:18987)
at batchedUpdates (react-dom.development.js:2210)
at dispatchEvent (react-dom.development.js:4946)
at interactiveUpdates$1 (react-dom.development.js:18974)
at interactiveUpdates (react-dom.development.js:2217)
at dispatchInteractiveEvent (react-dom.development.js:4923)
下面的示例代码:
const {useState} = React;
function App() {
const [name, setName] = useState('Mary');
const [age, setAge] = useState(16);
if (age < 16) {
return (
<div>
Name:{' '}
<input
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
<br />
Age:{' '}
<input
value={age}
type="number"
onChange={e => {
setAge(+e.target.value);
}}
/>
</div>
);
}
const [license, setLicense] = useState('A123456');
return (
<div>
Name:{' '}
<input
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
<br />
Age:{' '}
<input
value={age}
type="number"
onChange={e => {
setAge(+e.target.value);
}}
/>
<br />
Driver License:{' '}
<input
value={license}
onChange={e => {
setLicense(e.target.value);
}}
/>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
问题是,在第一次渲染中,调用了3个useState
挂钩-name
、age
和license
,但在年龄更改为低于16的值后,不再调用license
的useState
,导致只调用前2个挂钩。正如React文档所述:
不要在循环、条件或嵌套函数中调用Hook。相反,请始终在React函数的顶层使用Hooks。通过遵循此规则,可以确保每次渲染组件时都以相同的顺序调用Hook。这使得React能够在多个
useState
和useEffect
调用之间正确地保留Hooks的状态。
被调用的钩子的顺序很重要,如果我们编写导致钩子不被调用的代码,React将无法将钩子调用与其值相匹配。
解决方案是将license
挂钩移动到函数的顶部,以便无论是否需要都可以调用它。
const {useState} = React;
function App() {
const [name, setName] = useState('Mary');
const [age, setAge] = useState(16);
const [license, setLicense] = useState('A123456');
return (
<div>
Name:{' '}
<input
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
<br />
Age:{' '}
<input
value={age}
type="number"
onChange={e => {
setAge(+e.target.value);
}}
/>
{age >= 16 && <span>
<br />
Driver License:{' '}
<input
value={license}
onChange={e => {
setLicense(e.target.value);
}}
/></span>
}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
我已经通过更改React Hooks的顺序解决了这个问题。useEffect
出现在早期return
之后。
所以我更改了的代码
function (){
const [loading ,setLoading] = React.useState(false);
if (loading) {
return "loading ....."
}
useEffect(() => {
if (condition) {
doSomething();
}
}, []);
return <div>component</div>
}
至
function (){
const [loading ,setLoading] = React.useState(false);
useEffect(() => {
if (condition) {
doSomething();
}
}, []);
if (loading){
return "loading ....."
}
return <div>component</div>
}
所以,当加载变为真时,useEffect
将不会运行,这将引发错误。但如果我声明在useEffect
之后加载JSX元素,那么它就可以工作了。
确保您没有有条件地运行useEffect
。
例如,如果您有如下代码:
if(condition) {
useEffect(()=>{
doSomething();
}, []);
}
然后将其更改为
useEffect(()=>{
if(condition) {
doSomething();
}
}, []);
这样错误就不会发生了。
所以我也遇到了这个错误,但它与被包装在条件中的钩子无关。相反,这是由于我的键值不正确造成的。
我有一个组件,可以通过拖放将项目从一个列表移动到另一个列表。然而,其中一些项目被锁定了,所以我简单地删除了这些元素中对我的钩子的引用。
问题是,我使用索引作为键,因此当我在顶部拖动一个新元素时,它得到了与锁定元素相同的键,ref的值发生了更改,React抱怨出现了上述错误。
所以我对那些遇到这个问题的人的回答是——检查你是否有真正独特的钥匙!
今天我刚刚遇到这个错误,我通过将钩子useEffect()
的位置从函数的中间更改为头部来修复它。并确保不要在循环内使用钩子。
如果某些值在重新渲染之间没有改变,您可以告诉React跳过应用效果。为此,将数组作为可选的第二个参数传递给useEffect:
useEffect( () => {
dispatch({type: 'GET_PAGE', slug: Param});
setPagedata( data );
});
至
useEffect( () => {
dispatch({type: 'GET_PAGE', slug: Param});
}, [Param]);
useEffect( () => {
setPagedata( data );
}, [data]);
而且有效!
我在自己的react应用程序中遇到了上述错误,当时我在渲染组件中使用了useCallback
挂钩(关键是在if条件中(。将挂钩移出功能组件解决了错误。教训是:不要在条件语句中使用钩子。
如果有条件地渲染,请不要将组件属性数据放在两者之间!
if (splashScreen)
return <Spinner>Loading</Spinner>
//DO NOT PUT THEM HERE
return (
...
)
const useStyle = makeStyles((theme) => ({
subHeading: {
lineHeight: 1.2
},
listItemTextStyle: {
textTransform: "none"
}
}));
export const getOmniDetails = (data) => {
const classes = useStyle();
return (
<Typography className={classes.subHeading} component="p">
User Name: {data.userName}
</Typography>
);
};
export default function OmniSearchDropdown(props) {
const { users = [], openProfile } = props;
const classes = useStyle();
return (
<ListItemText
primary={
<Typography variant="h6" className={classes.listItemTextStyle}>
{r.displayName}
</Typography>
}
secondary={getOmniDetails(r)} // wrong
/>
);
}
当我在两个不同的组件中使用单个主题(useStyle
(时,我遇到了同样的问题,并且我将getOmniDetails()
作为函数调用,这是错误的。
我只是通过将函数转换为组件<GetOmniDetails data={data}/>
来解决这个问题。
如果打开热重新加载(HMR(并添加或删除useEffect,也可能发生这种情况。只需重新加载页面,错误将不会重现。
在我的例子中,我在带有条件的函数组件的返回部分直接使用useWindowDimensions钩子。通过在返回部分之外声明一个变量并在条件呈现中使用该变量来解决此问题。希望这能有所帮助!