不能在呈现不同组件警告时更新组件

我得到这个警告在反应:

index.js:1 Warning: Cannot update a component (`ConnectFunction`)
while rendering a different component (`Register`). To locate the
bad setState() call inside `Register`

我去了堆栈跟踪中指出的位置,并删除了所有设置状态,但警告仍然存在。这可能发生在redux调度?

我的代码:

register.js

class Register extends Component {
render() {
if( this.props.registerStatus === SUCCESS) {
// Reset register status to allow return to register page
this.props.dispatch( resetRegisterStatus())  # THIS IS THE LINE THAT CAUSES THE ERROR ACCORDING TO THE STACK TRACE
return <Redirect push to = {HOME}/>
}
return (
<div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
<RegistrationForm/>
</div>
);
}
}


function mapStateToProps( state ) {
return {
registerStatus: state.userReducer.registerStatus
}
}


export default connect ( mapStateToProps ) ( Register );

函数,该函数触发了由register.js调用的registerForm组件中的警告

handleSubmit = async () => {
if( this.isValidForm() ) {
const details = {
"username": this.state.username,
"password": this.state.password,
"email": this.state.email,
"clearance": this.state.clearance
}
await this.props.dispatch( register(details) )
if( this.props.registerStatus !== SUCCESS && this.mounted ) {
this.setState( {errorMsg: this.props.registerError})
this.handleShowError()
}
}
else {
if( this.mounted ) {
this.setState( {errorMsg: "Error - registration credentials are invalid!"} )
this.handleShowError()
}
}
}

加:

Stacktrace

269636 次浏览

我通过将分配从寄存器组件渲染方法移除到componentwillunmount方法来修复这个问题。这是因为我希望这个逻辑在重定向到登录页面之前发生。一般来说,最好的做法是把所有的逻辑都放在渲染方法之外,所以我的代码之前写得很糟糕。希望这能在将来对其他人有所帮助:)

我重构的寄存器组件:

class Register extends Component {


componentWillUnmount() {
// Reset register status to allow return to register page
if ( this.props.registerStatus !== "" ) this.props.dispatch( resetRegisterStatus() )
}


render() {
if( this.props.registerStatus === SUCCESS ) {
return <Redirect push to = {LOGIN}/>
}
return (
<div style = \{\{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
<RegistrationForm/>
</div>
);
}
}


此警告从React V16.3.0开始引入。

如果您正在使用功能组件,您可以将setState调用包装到useEffect中。

不能工作的代码:

const HomePage = (props) => {
    

props.setAuthenticated(true);


const handleChange = (e) => {
props.setSearchTerm(e.target.value.toLowerCase());
};


return (
<div key={props.restInfo.storeId} className="container-fluid">
<ProductList searchResults={props.searchResults} />
</div>
);
};

现在你可以把它改成:

const HomePage = (props) => {
// trigger on component mount
useEffect(() => {
props.setAuthenticated(true);
}, []);


const handleChange = (e) => {
props.setSearchTerm(e.target.value.toLowerCase());
};


return (
<div key={props.restInfo.storeId} className="container-fluid">
<ProductList searchResults={props.searchResults} />
</div>
);
};

我只是遇到了这个问题,在我意识到我做错了什么之前,我花了一些时间去挖掘——我只是没有注意我是如何写我的函数组件的。

我是这样做的:

const LiveMatches = (props: LiveMatchesProps) => {
const {
dateMatches,
draftingConfig,
sportId,
getDateMatches,
} = props;


if (!dateMatches) {
const date = new Date();
getDateMatches({ sportId, date });
};


return (<div>{component stuff here..}</div>);
};

在分派getDateMatches()的redux调用之前,我刚刚忘记使用useEffect

所以它应该是:

const LiveMatches = (props: LiveMatchesProps) => {
const {
dateMatches,
draftingConfig,
sportId,
getDateMatches,
} = props;


useEffect(() => {
if (!dateMatches) {
const date = new Date();
getDateMatches({ sportId, date });
}
}, [dateMatches, getDateMatches, sportId]);


return (<div>{component stuff here..}</div>);
};

我也面临着同样的问题,对我来说解决方法是如果你在做

setparam / setoption

在useEffect之外,那么这个问题就发生了。所以尝试在useEffect中做这些事情。这就像魅力一样管用

< >强TL,博士; 对于我的例子,我修复警告的方法是将useState更改为useRef

react_devtools_backend.js:2574 Warning: Cannot update a component (`Index`) while rendering a different component (`Router.Consumer`). To locate the bad setState() call inside `Router.Consumer`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Route (http://localhost:3000/main.bundle.js:126692:29)
at Index (http://localhost:3000/main.bundle.js:144246:25)
at Switch (http://localhost:3000/main.bundle.js:126894:29)
at Suspense
at App
at AuthProvider (http://localhost:3000/main.bundle.js:144525:23)
at ErrorBoundary (http://localhost:3000/main.bundle.js:21030:87)
at Router (http://localhost:3000/main.bundle.js:126327:30)
at BrowserRouter (http://localhost:3000/main.bundle.js:125948:35)
at QueryClientProvider (http://localhost:3000/main.bundle.js:124450:21)

我所做的上下文的完整代码(从带有// OLD:的行更改为它们上面的行)。然而这没关系,只要试着从__ABC1改为useRef!!

import { HOME_PATH, LOGIN_PATH } from '@/constants';
import { NotFoundComponent } from '@/routes';
import React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
import { useAccess } from '@/access';
import { useAuthContext } from '@/contexts/AuthContext';
import { AccessLevel } from '@/models';


type Props = RouteProps & {
component: Exclude<RouteProps['component'], undefined>;
requireAccess: AccessLevel | undefined;
};


export const Index: React.FC<Props> = (props) => {
const { component: Component, requireAccess, ...rest } = props;


const { isLoading, isAuth } = useAuthContext();
const access = useAccess();
const mounted = React.useRef(false);
// OLD: const [mounted, setMounted] = React.useState(false);


return (
<Route
{...rest}
render={(props) => {
// If in indentifying authentication state as the page initially loads, render a blank page
if (!mounted.current && isLoading) return null;
// OLD: if (!mounted && isLoading) return null;


// 1. Check Authentication is one step
if (!isAuth && window.location.pathname !== LOGIN_PATH)
return <Redirect to={LOGIN_PATH} />;
if (isAuth && window.location.pathname === LOGIN_PATH)
return <Redirect to={HOME_PATH} />;


// 2. Authorization is another
if (requireAccess && !access[requireAccess])
return <NotFoundComponent />;


mounted.current = true;
// OLD: setMounted(true);
return <Component {...props} />;
}}
/>
);
};


export default Index;

如果你的情况是useEffect不能被使用,或者如果错误是不是因为Redux

我使用setTimeout将两个useState变量中的一个重定向到回调队列。

我有一个父组件和一个子组件,每个组件中都有useState变量。解决方案是使用setTimeout包装useState变量:

setTimeout(() => SetFilterData(data), 0);

在下面的例子

父组件

import ExpenseFilter from '../ExpensesFilter'
    

function ExpensesView(props) {
    

const [filterData, SetFilterData] = useState('')
    

const GetFilterData = (data) => {
// SetFilterData(data);


//*****WRAP useState VARIABLE INSIDE setTimeout WITH 0 TIME AS BELOW.*****
setTimeout(() => SetFilterData(data), 0);
    

}
    

const filteredArray = props.expense.filter(expenseFiltered =>
expenseFiltered.dateSpent.getFullYear().toString() === filterData);
    

    

return (
<Window>
<div>
<ExpenseFilter FilterYear = {GetFilterData}></ExpenseFilter>

子组件

const ExpensesFilter = (props) => {
    

const [filterYear, SetFilterYear] = useState('2022')
    

const FilterYearListener = (event) => {
event.preventDefault()
SetFilterYear(event.target.value)
}
    

props.FilterYear(filterYear)
    

return (

我的例子。

带有错误的代码:

<Form
initialValues=\{\{ ...kgFormValues, dataflow: dataflows.length > 0 ? dataflows[0].df_tpl_key : "" }}
onSubmit={() => {}}
render={({values, dirtyFields }: any) => {
      

const kgFormValuesUpdated = {
proj_key: projectKey,
name: values.name,
description: values.description,
public: values.public,
dataflow: values.dataflow,
flavours: flavoursSelected,
skipOCR: values.skipOCR
};


if (!_.isEqual(kgFormValues, kgFormValuesUpdated)) {
setNewKgFormValues(kgFormValuesUpdated);
}

工作代码:

 <Form
initialValues=\{\{ ...kgFormValues, dataflow: dataflows.length > 0 ? dataflows[0].df_tpl_key : "" }}
onSubmit={() => {}}
render={({ values, dirtyFields }: any) => {
useEffect(() => {
const kgFormValuesUpdated = {
proj_key: projectKey,
name: values.name,
description: values.description,
public: values.public,
dataflow: values.dataflow,
flavours: flavoursSelected,
skipOCR: values.skipOCR
};


if (!_.isEqual(kgFormValues, kgFormValuesUpdated)) {
setNewKgFormValues(kgFormValuesUpdated);
}
}, [values]);


return (

最小复制示例

我有点困惑到底是什么引发了这个问题,有一个最小的立即可运行的例子帮助我更好地理解它:

index . html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.14.7/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function NotMain(props) {
props.setN(1)
return <div>NotMain</div>
}


function Main(props) {
const [n, setN] = React.useState(0)
return <>
<NotMain setN={setN} />
<div>Main {n}</div>
</>
}


ReactDOM.render(
<Main/>,
document.getElementById('root')
);
</script>
</body>
</html>

错误失败:

react-dom.development.js:61警告:在呈现不同的组件(' NotMain ')时不能更新一个组件(' Main ')。要定位' NotMain '内部错误的setState()调用,请按照https://reactjs.org/link/setstate-in-render中描述的堆栈跟踪

后面跟着一个堆栈跟踪:

    at NotMain (<anonymous>:16:9)
at Main (<anonymous>:21:31)

大概16:9是调用props.setN(1)的确切行,但由于Babel JSX翻译,行号有点混乱。

解决方案和其他答案一样,是这样做的:

function NotMain(props) {
React.useEffect(() => { props.setN(1) }, [])
return <div>NotMain</div>
}

直观地说,我认为这个错误发生的大致原因是:

你不应该从渲染方法中更新状态,否则会导致不同的结果,这取决于React如何渲染事物的内部顺序。

当使用功能组件时,方法是使用钩子。在我们的例子中,useEffect将在渲染完成后运行,所以我们可以从那里开始执行。

当使用类时,这一点变得更加清晰,例如:

然而,当使用功能组件时,从概念上讲,事情有点复杂,因为组件函数既是呈现,也是设置回调的代码。

请仔细阅读错误消息,我的是指向签入组件有一个坏的setState。当我检查时,我有一个不是箭头函数的onpress。

是这样的:

onPress={navigation.navigate("Home", { screen: "HomeScreen" })}

我把它改成了这样:

onPress={() => navigation.navigate("Home", { screen: "HomeScreen" }) }

我的错误信息是:

警告:不能更新组件 (ForwardRef(BaseNavigationContainer)),同时渲染不同的 组件(SignIn)。来定位内部错误的setState()调用 SignIn,按照中所描述的堆栈跟踪 https://reactjs.org/link/setstate-in-render in SignIn (at SignInScreen.tsx:20)

如果你使用反应导航并且你正在使用setParamssetOptions,你必须把它们放在类组件的componentDidMount()方法中或函数组件的useEffects()钩子中。

我认为这很重要。 @Red-Baron从这个帖子中指出:

@machineghost:我认为你误解了这条信息警告的内容。

将更新父节点状态的回调传递给子节点并没有什么错。这一直都很好。

问题是当一个组件在另一个组件中排队更新时,而第一个组件正在呈现。

换句话说,不要这样做:

function SomeChildComponent(props) {
props.updateSomething();
return <div />
}

但这是可以的:

function SomeChildComponent(props) {
// or make a callback click handler and call it in there
return <button onClick={props.updateSomething}>Click Me</button>
}

而且,正如Dan多次指出的那样,在渲染时在同一个组件中排队更新也很好:

function SomeChildComponent(props) {
const [number, setNumber] = useState(0);


if(props.someValue > 10 && number < 5) {
// queue an update while rendering, equivalent to getDerivedStateFromProps
setNumber(42);
}


return <div>{number}</div>
}

使用上面的一些答案,我摆脱了以下错误:

if (value === "newest") {
dispatch(sortArticlesNewest());
} else {
dispatch(sortArticlesOldest());
}

这段代码位于我的组件顶层

    const SelectSorting = () => {
const dispatch = useAppDispatch();


const {value, onChange} = useSelect();


useEffect(() => {
if (value === "newest") {
dispatch(sortArticlesNewest());
} else {
dispatch(sortArticlesOldest());
}
}, [dispatch, value]);
使用React和材质UI (MUI) 我改变了我的代码来自:

<IconButton onClick={setOpenDeleteDialog(false)}>
<Close />
</IconButton>

:

<IconButton onClick={() => setOpenDeleteDialog(false)}>
<Close />
</IconButton>

简单的修理

我也有同样的问题。我设置了一些存储函数的状态,像这样:

// my state definition
const [onConfirm, setOnConfirm] = useState<() => void>();


// then I used this piece of code to update the state
function show(onConfirm: () => void) {
setOnConfirm(onConfirm);
}

问题来自setOnConfirm。在React中,setState可以接受新值,也可以接受返回新值的函数。在这种情况下,React希望通过调用onConfirm来获得新的状态,这是不正确的。

改成这个解决了我的问题:

setOnConfirm(() => onConfirm);

在GitHub中遇到类似的问题后,我能够解决这个问题,这使我找到这样的评论,显示如何精确定位导致错误的文件中的确切行。我不知道堆栈跟踪在那里。希望这能帮助到一些人!

请看下面我的解决方案。我只是将函数转换为使用回调。

旧的代码

function TopMenuItems() {
const dispatch = useDispatch();


function mountProjectListToReduxStore(projects) {
const projectDropdown = projects.map((project) => ({
id: project.id,
name: project.name,
organizationId: project.organizationId,
createdOn: project.createdOn,
lastModifiedOn: project.lastModifiedOn,
isComplete: project.isComplete,
}));
projectDropdown.sort((a, b) => a.name.localeCompare(b.name));
dispatch(loadProjectsList(projectDropdown));
dispatch(setCurrentOrganizationId(projectDropdown[0].organizationId));
}
};

新代码

function TopMenuItems() {
const dispatch = useDispatch();


const mountProjectListToReduxStore = useCallback((projects) => {
const projectDropdown = projects.map((project) => ({
id: project.id,
name: project.name,
organizationId: project.organizationId,
createdOn: project.createdOn,
lastModifiedOn: project.lastModifiedOn,
isComplete: project.isComplete,
}));
projectDropdown.sort((a, b) => a.name.localeCompare(b.name));
dispatch(loadProjectsList(projectDropdown));
dispatch(setCurrentOrganizationId(projectDropdown[0].organizationId));
}, [dispatch]);
};

我的案例是使用setState回调,而不是setState + useEffect

坏❌

  const closePopover = useCallback(
() =>
setOpen((prevOpen) => {
prevOpen && onOpenChange(false);
return false;
}),
[onOpenChange]
);

好✅

  const closePopover = useCallback(() => setOpen(false), []);


useEffect(() => onOpenChange(isOpen), [isOpen, onOpenChange]);