Recibo esta advertencia en reaccionar:
index.js:1 Warning: Cannot update a component (`ConnectFunction`) while rendering a different component (`Register`). To locate the bad setState() call inside `Register`Fui a las ubicaciones indicadas en el seguimiento de la pila y eliminé todos los estados establecidos, pero la advertencia aún persiste. ¿Es posible que esto pueda ocurrir desde el envío de redux?
mi código:
registro.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 );función que activa la advertencia en mi componente registerForm llamado por register.js
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() } } }seguimiento de pila:
mi ejemplo
Código con ese error:
<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); }Código de trabajo:
<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 (TL;RD; Para mi caso, lo que hice para arreglar la advertencia fue cambiar de useState a 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) El código completo para el contexto de lo que hice (cambiado de las líneas con // OLD: a la línea sobre ellas). Sin embargo , esto no importa, ¡solo intente cambiar de useState a 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;Estaba enfrentando el mismo problema, la solución funcionó para mí fue si estás haciendo
setParams/setOptions
fuera de useEffect entonces este problema está ocurriendo. Así que intenta hacer esas cosas dentro de useEffect. Funcionará a las mil maravillas.
Ejemplo de reproducción mínima
Estaba un poco confundido en cuanto a qué desencadena exactamente el problema, tener un ejemplo mínimo ejecutable inmediatamente me ayudó a comprenderlo un poco mejor:
índice.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>falla con error:
react-dom.development.js:61 Advertencia: No se puede actualizar un componente (`Main`) mientras se renderiza un componente diferente (`NotMain`). Para ubicar la llamada incorrecta a setState() dentro de `NotMain`, siga el seguimiento de la pila como se describe en https://reactjs.org/link/setstate-in-render
seguido de un seguimiento de la pila:
at NotMain (<anonymous>:16:9) at Main (<anonymous>:21:31) Presumiblemente, 16: 9 sería la línea exacta desde donde se llama a props.setN(1) , pero los números de línea están un poco desordenados debido a la traducción de Babel JSX.
La solución, como dicen muchas otras respuestas, es hacer en su lugar:
function NotMain(props) { React.useEffect(() => { props.setN(1) }, []) return <div>NotMain</div> }Intuitivamente, creo que la idea general de por qué ocurre este error es que:
Se supone que no debe actualizar el estado de los métodos de procesamiento, de lo contrario, podría generar resultados diferentes según el orden interno de cómo React procesa las cosas.
y cuando se usan componentes funcionales, la forma de hacerlo es usar ganchos. En nuestro caso, useEffect se ejecutará después de que se complete el procesamiento, por lo que podemos hacerlo desde allí.
Al usar clases, esto se vuelve un poco más claro y se preguntó, por ejemplo, en:
Sin embargo, cuando se usan componentes funcionales, las cosas son conceptualmente un poco más mixtas, ya que la función del componente es tanto el renderizado como el código que configura las devoluciones de llamada.
Si useEffect no se puede usar en su caso o si el error NO se debe a Redux
setTimeout para redirigir una de las dos variables useState a la cola de devolución de llamada.
Tengo un componente principal y uno secundario con la variable useState en cada uno de ellos. La solución es envolver la variable useState usando setTimeout :
setTimeout(() => SetFilterData(data), 0);Ejemplo a continuación
Componente principal
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>Componente secundario
const ExpensesFilter = (props) => { const [filterYear, SetFilterYear] = useState('2022') const FilterYearListener = (event) => { event.preventDefault() SetFilterYear(event.target.value) } props.FilterYear(filterYear) return (Creo que esto es importante. Es de esta publicación que @Red-Baron señaló:
@machineghost: Creo que no comprende de qué advierte el mensaje.
No hay nada de malo en pasar devoluciones de llamada a los niños que actualizan el estado en los padres. Eso siempre ha estado bien.
El problema es cuando un componente pone en cola una actualización en otro componente, mientras el primer componente se está procesando.
En otras palabras, no hagas esto:
function SomeChildComponent(props) { props.updateSomething(); return <div /> }Pero esto está bien:
function SomeChildComponent(props) { // or make a callback click handler and call it in there return <button onClick={props.updateSomething}>Click Me</button> }Y, como Dan ha señalado varias veces, poner en cola una actualización en el mismo componente mientras se renderiza también está bien:
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> }lea detenidamente el mensaje de error, el mío apuntaba a un componente de inicio de sesión que tenía un mal setState. que cuando examiné, tenía un onpress que no era una función de flecha.
fue así:
onPress={navigation.navigate("Home", { screen: "HomeScreen" })}Lo cambié a esto:
onPress={() => navigation.navigate("Home", { screen: "HomeScreen" }) }Mi mensaje de error fue:
Advertencia: no se puede actualizar un componente (
ForwardRef(BaseNavigationContainer)) mientras se procesa un componente diferente (SignIn). Para ubicar la llamada incorrecta a setState() dentro deSignIn, siga el seguimiento de la pila como se describe en https://reactjs.org/link/setstate-in-render en SignIn (en SignInScreen.tsx:20)
Si usa React Navigation y está usando setParams o setOptions , debe colocarlos dentro del método componentDidMount() de los componentes de la clase o en el gancho useEffects() de los componentes funcionales.
Solo vine aquí porque tuve este problema y me tomó un poco de investigación antes de darme cuenta de lo que había hecho mal: simplemente no estaba prestando atención a cómo estaba escribiendo mi componente funcional.
Pensé en dejar una respuesta aquí en caso de que alguien viniera a buscar, y cometieron el mismo error simple que yo cometí.
estaba haciendo esto:
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>); }; Me había olvidado de usar useEffect antes de enviar mi llamada redux de getDateMatches()
Tan estúpido y algo que había estado haciendo en todos los demás componentes, jaja.
Entonces debería haber sido:
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>); };Error simple y tonto, pero tomó un tiempo darse cuenta, así que espero que esto ayude a alguien más con este problema.
Esto es Loco. Pero intente deshabilitar las herramientas de desarrollo de Redux... Me apareció esta advertencia en un buen código debido a las herramientas de desarrollo de Redux. Encontré esto después de investigar por qué mis acciones se activaban varias veces a pesar de que solo lo envié una vez. El reductor de Redux se ejecuta varias veces cuando se envía por primera vez una acción no utilizada
Esta advertencia se introdujo desde React V16.3.0.
Si está utilizando componentes funcionales, puede envolver la llamada setState en useEffect.
Código que no funciona:
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> ); };Ahora puedes cambiarlo a:
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> ); };Solucioné este problema eliminando el envío del método de renderizado de componentes de registro al método de desmontaje del componente. Esto se debe a que quería que esta lógica ocurriera justo antes de redirigir a la página de inicio de sesión. En general, es una buena práctica poner toda su lógica fuera del método de renderizado, por lo que mi código estaba mal escrito antes. Espero que esto ayude a alguien más en el futuro :)
Mi componente de registro refactorizado:
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> ); } }