setState () dentro de componentDidUpdate ()

131

Estoy escribiendo un script que mueve el menú desplegable por debajo o por encima de la entrada, dependiendo de la altura del menú desplegable y la posición de la entrada en la pantalla. También quiero configurar el modificador en el menú desplegable de acuerdo con su dirección. Pero usar setStatedentro de la componentDidUpdatecrea un bucle infinito (que es obvio)

He encontrado una solución al usar getDOMNodey configurar classname para desplegable directamente, pero creo que debería haber una mejor solución usando las herramientas React. Alguien puede ayudarme?

Aquí hay una parte del código de trabajo con getDOMNode(una lógica de posicionamiento un poco descuidada para simplificar el código)

let SearchDropdown = React.createClass({
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        el.classList.remove('dropDown-top');
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            el.classList.add('dropDown-top');
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        return (
            <DropDown >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

y aquí hay código con setstate (que crea un bucle infinito)

let SearchDropdown = React.createClass({
    getInitialState() {
        return {
            top: false
        };
    },
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        if (this.state.top) {
           this.setState({top: false});
        }
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            if (!this.state.top) {
              this.setState({top: true});
           }
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        let class = cx({'dropDown-top' : this.state.top});
        return (
            <DropDown className={class} >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});
Katerina Pavlenko
fuente
9
Creo que el truco aquí es que setStatehabrá siempre desencadenar una re-render. En lugar de verificar state.topy llamar setStatevarias veces, solo haga un seguimiento de lo que desea state.topque esté en una variable local, luego una vez al final de la componentDidUpdatellamada setStatesolo si su variable local no coincide state.top. Tal como está ahora, reinicia inmediatamente state.topdespués del primer renderizado, lo que lo coloca en el ciclo infinito.
Randy Morris
2
Vea las dos implementaciones diferentes de componentDidUpdateen este violín .
Randy Morris
¡maldición! La variable local resuelve todo el problema, ¡cómo no lo había resuelto mysef! ¡Gracias!
Katerina Pavlenko
1
Creo que deberías aceptar la respuesta a continuación. Si lo lees de nuevo, creo que encontrarás que responde la pregunta inicial lo suficiente.
Randy Morris
¿Por qué nadie ha sugerido trasladar la condición componentShouldUpdate?
Patrick Roberts

Respuestas:

116

Puedes usar setStatedentro componentDidUpdate. El problema es que de alguna manera estás creando un ciclo infinito porque no hay condición de ruptura.

Según el hecho de que necesita valores proporcionados por el navegador una vez que se procesa el componente, creo que su enfoque sobre el uso componentDidUpdatees correcto, solo necesita un mejor manejo de la condición que desencadena el setState.

damianmr
fuente
44
¿Qué quieres decir con "condición de descanso"? comprobar si el estado ya está configurado y no restablecerlo?
Katerina Pavlenko
Estoy de acuerdo con esto, mi único comentario adicional sería que la adición / eliminación de clases es probablemente innecesaria componentDidUpdatey solo se puede agregar según sea necesario render.
Randy Morris
pero la adición / eliminación de clases depende de la posición desplegable que se verifica en componentDidUpdate, ¿sugiere que lo verifique dos veces? Y según tengo entendido, componentDidUpdate se llama DESPUÉS de render (), por lo que es inútil agregar / eliminar clase en render ()
Katerina Pavlenko
He agregado mi código con setstate, ¿puede verificarlo y señalarme mi error? o muéstrame algún ejemplo que no cause un bucle
Katerina Pavlenko
2
componentDidUpdate (prevProps, prevState) {if (prevState.x! == this.state.x) {// Do Something}}
Ashok R
69

La componentDidUpdatefirma es void::componentDidUpdate(previousProps, previousState). Con esto podrás probar qué accesorios / estado están sucios y llamar en setStateconsecuencia.

Ejemplo:

componentDidUpdate(previousProps, previousState) {
    if (previousProps.data !== this.props.data) {
        this.setState({/*....*/})
    }
}
Abdennour TOUMI
fuente
componentDidMountno tiene ningún argumento, y solo se llama cuando se crea el componente, por lo que no se puede usar para el propósito descrito.
Jules
@Jules Gracias! Solía ​​escribir componentDidMount, así que cuando escribí la respuesta el famoso nombre cayó en cascada 😮 Nuevamente, ¡Gracias y genial ponerse al día!
Abdennour TOUMI
componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
Ashok R
Sé tu preocupación @AshokR. Reduce el nombre arg. pero "prev" puede significar prevenir no anterior .. hhh. .kidding :)
Abdennour TOUMI
58

Si lo usa setStatedentro componentDidUpdate, actualiza el componente, lo que resulta en una llamada a la componentDidUpdateque posteriormente setStatevuelve a llamar, lo que resulta en un bucle infinito. Debe llamar condicionalmente setStatey asegurarse de que la condición que viola la llamada ocurra eventualmente, por ejemplo:

componentDidUpdate: function() {
    if (condition) {
        this.setState({..})
    } else {
        //do something else
    }
}

En caso de que solo esté actualizando el componente enviándole accesorios (no lo está actualizando setState, a excepción del caso dentro de componentDidUpdate), puede llamar a setStateinside en componentWillReceivePropslugar de componentDidUpdate.

mickeymoon
fuente
2
pregunta anterior, pero componentWillReceiveProps está en desuso y debe usarse componentWillRecieveProps. No puede establecer State dentro de este método.
Brooks DuBois
¿Quiere decir getDerivedStateFromProps.
adi518
5

Este ejemplo lo ayudará a comprender los ganchos del ciclo de vida de React .

Puede setStateen getDerivedStateFromPropsel método es decir, staticy desencadenar el método después de apoyos cambian en componentDidUpdate.

En componentDidUpdateobtendrá 3er parámetro que regresa de getSnapshotBeforeUpdate.

Puedes consultar este enlace de códigos y casillas

// Child component
class Child extends React.Component {
  // First thing called when component loaded
  constructor(props) {
    console.log("constructor");
    super(props);
    this.state = {
      value: this.props.value,
      color: "green"
    };
  }

  // static method
  // dont have access of 'this'
  // return object will update the state
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps");
    return {
      value: props.value,
      color: props.value % 2 === 0 ? "green" : "red"
    };
  }

  // skip render if return false
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    // return nextState.color !== this.state.color;
    return true;
  }

  // In between before real DOM updates (pre-commit)
  // has access of 'this'
  // return object will be captured in componentDidUpdate
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate");
    return { oldValue: prevState.value };
  }

  // Calls after component updated
  // has access of previous state and props with snapshot
  // Can call methods here
  // setState inside this will cause infinite loop
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate: ", prevProps, prevState, snapshot);
  }

  static getDerivedStateFromError(error) {
    console.log("getDerivedStateFromError");
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    console.log("componentDidCatch: ", error, info);
  }

  // After component mount
  // Good place to start AJAX call and initial state
  componentDidMount() {
    console.log("componentDidMount");
    this.makeAjaxCall();
  }

  makeAjaxCall() {
    console.log("makeAjaxCall");
  }

  onClick() {
    console.log("state: ", this.state);
  }

  render() {
    return (
      <div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>
        <p style={{ color: this.state.color }}>Color: {this.state.color}</p>
        <button onClick={() => this.onClick()}>{this.props.value}</button>
      </div>
    );
  }
}

// Parent component
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 1 };

    this.tick = () => {
      this.setState({
        date: new Date(),
        value: this.state.value + 1
      });
    };
  }

  componentDidMount() {
    setTimeout(this.tick, 2000);
  }

  render() {
    return (
      <div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>
        <p>Parent</p>
        <Child value={this.state.value} />
      </div>
    );
  }
}

function App() {
  return (
    <React.Fragment>
      <Parent />
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Nikhil Mahirrao
fuente
2

Diría que debe verificar si el estado ya tiene el mismo valor que está tratando de establecer. Si es lo mismo, no tiene sentido volver a establecer el estado para el mismo valor.

Asegúrese de configurar su estado así:

let top = newValue /*true or false*/
if(top !== this.state.top){
    this.setState({top});
}
gradosevic
fuente
-1

Tuve un problema similar en el que tengo que centrar la información sobre herramientas. React setState en componentDidUpdate me puso en bucle infinito, traté de que funcionara. Pero descubrí que usar la devolución de llamada de referencia me dio una solución más simple y limpia, si usa la función en línea para la devolución de llamada de referencia, se enfrentará al problema nulo para cada actualización de componentes. Por lo tanto, use la referencia de función en la devolución de llamada de referencia y establezca el estado allí, que iniciará el renderizado

Sanjay
fuente