¿Puedes forzar a un componente React a volver a reproducir sin llamar a setState?

689

Tengo un objeto externo (al componente) observable en el que quiero escuchar los cambios. Cuando el objeto se actualiza, emite eventos de cambio, y luego quiero volver a procesar el componente cuando se detecta cualquier cambio.

Con un nivel superior React.renderesto ha sido posible, pero dentro de un componente no funciona (lo cual tiene sentido ya que el rendermétodo solo devuelve un objeto).

Aquí hay un ejemplo de código:

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

Al hacer clic internamente en el botón se llama this.render(), pero eso no es lo que realmente hace que se produzca la representación (puede ver esto en acción porque el texto creado por {Math.random()}no cambia). Sin embargo, si simplemente llamo en this.setState()lugar de this.render(), funciona bien.

Entonces, supongo que mi pregunta es: ¿los componentes React deben tener un estado para volver a reproducir? ¿Hay alguna forma de obligar al componente a actualizarse a demanda sin cambiar el estado?

Philip Walton
fuente
77
la respuesta aceptada dice que this.forceUpdate()es la solución correcta, mientras que el resto de todas las respuestas y varios comentarios están en contra del uso forceUpdate(). ¿Entonces estará bien decir que la pregunta aún no obtuvo una solución / respuesta adecuada?
adi
66
La respuesta exceptuada respondió mi pregunta en ese momento. Es técnicamente la respuesta que estaba buscando, y sigo pensando que es la respuesta correcta. Las otras respuestas creo que son una buena información complementaria para las personas con la misma pregunta a tener en cuenta.
Philip Walton
Es interesante notar que NO NECESITA NADA EN EL ESTADO que no sea inicializarlo en un objeto simple, y luego llamar a this.setState ({}) solo activa un nuevo render. Reaccionar es genial, pero también extraño a veces. Por lo tanto, puede recorrer directamente los datos de una tienda cuando se activa un cambio sin necesidad de tuberías adicionales o preocuparse por los datos por instancia de componente.
Jason Sebring
En general, diría que sí. Si está utilizando la actualización forzada, esto es para actualizar componentes donde pueden depender de cambios fuera de la administración de estado de su aplicación. No puedo pensar en un buen ejemplo de eso. Aunque es útil saberlo.
Daniel

Respuestas:

765

En su componente, puede llamar this.forceUpdate()para forzar una rendición.

Documentación: https://facebook.github.io/react/docs/component-api.html

Crob
fuente
169
Otra forma es this.setState (this.state);
kar
25
Se desaconseja el uso de forceupdate, consulte la última respuesta.
Varand Pezeshkian
42
Si bien this.forceUpdate()no es una muy buena solución para el problema de los solicitantes, es la respuesta correcta a la pregunta planteada en el título. Si bien generalmente debe evitarse, hay situaciones en las que forceUpdate es una buena solución.
ArneHugo
8
@kar En realidad, se puede implementar lógica adicional para shouldComponentUpdate()hacer que su solución sea inútil.
Qwerty
44
Se excedió el tamaño máximo de la pila de llamadas
IntoTheDeep
326

forceUpdatedebe evitarse porque se desvía de una mentalidad React. Los documentos de React citan un ejemplo de cuándo forceUpdatepodría usarse:

Por defecto, cuando el estado o los accesorios de su componente cambian, su componente se volverá a representar. Sin embargo, si estos cambian implícitamente (por ejemplo: los datos profundos dentro de un objeto cambian sin cambiar el objeto mismo) o si su método render () depende de otros datos, puede decirle a React que necesita volver a ejecutar render () llamando forceUpdate ().

Sin embargo, me gustaría proponer la idea de que incluso con objetos profundamente anidados, forceUpdatees innecesario. Al usar una fuente de datos inmutable, los cambios de seguimiento se vuelven baratos; un cambio siempre dará como resultado un nuevo objeto, por lo que solo necesitamos verificar si la referencia al objeto ha cambiado. Puede usar la biblioteca Immutable JS para implementar objetos de datos inmutables en su aplicación.

Normalmente, debe intentar evitar todos los usos de forceUpdate () y solo leer de this.props y this.state en render (). Esto hace que su componente sea "puro" y que su aplicación sea mucho más simple y eficiente. forceUpdate ()

Cambiar la clave del elemento que desea volver a representar funcionará. Establezca la clave de apoyo en su elemento a través del estado y luego cuando desee actualizar el estado establecido para tener una nueva clave.

<Element key={this.state.key} /> 

Luego se produce un cambio y restablece la clave

this.setState({ key: Math.random() });

Quiero señalar que esto reemplazará el elemento en el que está cambiando la clave. Un ejemplo de dónde esto podría ser útil es cuando tiene un campo de entrada de archivo que desea restablecer después de cargar una imagen.

Si bien la verdadera respuesta a la pregunta del OP sería forceUpdate()que he encontrado esta solución útil en diferentes situaciones. También quiero señalar que si te encuentras usando forceUpdatees posible que desees revisar tu código y ver si hay otra forma de hacer las cosas.

NOTA 1-9-2019:

Lo anterior (cambiando la clave) reemplazará completamente el elemento. Si te encuentras actualizando la clave para que los cambios sucedan, probablemente tengas un problema en otro lugar de tu código. Usar Math.random()in key recreará el elemento con cada render. NO recomendaría actualizar la clave de esta manera, ya que react utiliza la clave para determinar la mejor manera de volver a representar las cosas.

pizzarob
fuente
16
Me interesaría saber por qué esto recibió un voto negativo. He estado golpeándome la cabeza tratando de hacer que los lectores de pantalla respondan a las alertas de aria y esta es la única técnica que he encontrado que funciona de manera confiable. Si tiene algo en su interfaz de usuario que genera la misma alerta cada vez que se hace clic, por defecto, reaccionar no vuelve a representar el DOM, por lo que el lector de pantalla no anuncia el cambio. Establecer una clave única lo hace funcionar. Tal vez el voto negativo se debió a que todavía implica establecer el estado. ¡Pero forzar un re-renderizado a DOM configurando la tecla es dorado!
Martin Bayly
2
Me gustó mucho esta respuesta porque - 1: se desaconseja el uso de forceupdate (), y 2: de esta manera es muy útil para componentes de terceros que haya instalado a través de npm, lo que significa que no son sus propios componentes. Por ejemplo, React-redimensionable y móvil es un componente de terceros que utilizo, pero no está reaccionando a mi setsette de componentes. Esta es una gran solución.
Varand Pezeshkian
80
Este es un hack y un abuso de key. Primero, su intención no está clara. Un lector de su código tendrá que trabajar duro para comprender por qué lo está utilizando de keyesta manera. En segundo lugar, esto no es más puro que forceUpdate. Reacción "pura" significa que la presentación visual de su componente depende 100% de su estado, por lo que si cambia algún estado, se actualiza. Sin embargo, si tiene algunos objetos profundamente anidados (el escenario que los forceUpdatedocumentos citan una razón para usarlo), entonces el uso lo forceUpdatedeja claro. Tercero, Math.random()es ... al azar. Teóricamente, podría generar el mismo número aleatorio.
atomkirk
8
@xtraSimplicity No puedo estar de acuerdo con que esto cumpla con la forma React de hacer las cosas. forceUpdatees la forma de reaccionar de hacer esto.
Rob Grant
3
@Robert Grant, mirando hacia atrás, estoy de acuerdo. La complejidad añadida realmente no vale la pena.
XtraSimplicity
80

En realidad, forceUpdate()es la única solución correcta, ya que setState()podría no desencadenar una nueva representación si se implementa una lógica adicional shouldComponentUpdate()o simplemente cuando regresa false.

forceUpdate ()

Llamar forceUpdate()hará render()que se llame al componente, omitiendo shouldComponentUpdate(). más...

setState ()

setState()siempre activará una nueva representación a menos que se implemente una lógica de representación condicional shouldComponentUpdate(). más...


forceUpdate() puede ser llamado desde su componente por this.forceUpdate()


Ganchos: ¿Cómo puedo forzar que el componente se vuelva a renderizar con ganchos en React?


Por cierto: ¿estás mutando o tus propiedades anidadas no se propagan?

QWERTY
fuente
1
Una cosa interesante a tener en cuenta es que las asignaciones de estado fuera de setState como this.state.foo = 'bar' no activarán el método de ciclo de vida de renderizado
lfender6445
44
@ lfender6445 La asignación de estado directamente con el this.stateexterior del constructor también debería arrojar un error. Podría no haberlo hecho en 2016; pero estoy bastante seguro de que lo hace ahora.
Tyler
He agregado algunos enlaces para evitar asignaciones directas de estado.
Qwerty
36

Evité forceUpdatehaciendo lo siguiente

CAMINO INCORRECTO : no utilice el índice como clave

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

MANERA CORRECTA : use la identificación de datos como clave, puede ser una guía, etc.

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

entonces, al hacer tal mejora de código, su componente será ÚNICO y se renderizará naturalmente

vijay
fuente
this.state.rows.map((item) => <MyComponent item={item} key={item.id} /> )
Tal
Muchas gracias por empujarme en la dirección correcta. Usar el índice como clave fue mi problema.
RoiEX
Para justificar mi voto negativo, a pesar de que es una buena práctica utilizar este enfoque, esta respuesta no está relacionada con la pregunta.
micnic
34

Cuando desee que se comuniquen dos componentes React, que no están vinculados por una relación (padre-hijo), es recomendable utilizar Flux o arquitecturas similares.

Lo que desea hacer es escuchar los cambios del almacén de componentes observables , que contiene el modelo y su interfaz, y guardar los datos que hacen que el render cambie como stateen MyComponent. Cuando la tienda envía los nuevos datos, cambia el estado de su componente, lo que activa automáticamente el renderizado.

Normalmente debes tratar de evitar el uso forceUpdate(). De la documentación:

Normalmente, debe intentar evitar todos los usos de forceUpdate () y solo leer de this.props y this.state en render (). Esto hace que su aplicación sea mucho más simple y eficiente

gcedo
fuente
2
¿Cuál es el estado de la memoria de un estado componente? Si tengo cinco componentes en una página y todos están escuchando una sola tienda, ¿tengo los datos cinco veces en la memoria o son referencias? ¿Y no todos esos oyentes se suman rápidamente? ¿Por qué es mejor "gotear" que simplemente pasar datos a su objetivo?
AJFarkas
55
En realidad, las recomendaciones son pasar los datos de la tienda a accesorios de componentes y solo usar el estado del componente para cosas como el estado de desplazamiento y otras cosas menores específicas de la interfaz de usuario. Si usa redux (lo recomiendo), puede usar la función de conexiónreact-redux para asignar automáticamente el estado de la tienda a los accesorios del componente siempre que sea necesario en función de una función de mapeo que proporcione.
Stijn de Witt
1
@AJFarkas el estado se asignaría a los datos de una tienda, que de todos modos es solo un puntero para que la memoria no sea un problema a menos que esté clonando.
Jason Sebring
44
"forceUpdate () siempre debe evitarse" es incorrecto. La documentación dice claramente: "Normalmente, debe intentar evitar todos los usos de forceUpdate ()". Hay casos de uso válidos deforceUpdate
AJP
2
@AJP tienes toda la razón, eso fue un sesgo personal al hablar :) He editado la respuesta.
gcedo
18

use ganchos o HOC elija

Usando ganchos o el patrón HOC (componente de orden superior) , puede tener actualizaciones automáticas cuando cambian sus tiendas. Este es un enfoque muy ligero sin un marco.

useStore Hooks forma de manejar las actualizaciones de la tienda

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withStores HOC maneja las actualizaciones de la tienda

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            // each store has a common change event to subscribe to
            store.on('change', this.onChange);
          });
      };

      componentWillUnmount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            store.off('change', this.onChange);
          });
      };

      render() {
        return (
          <WrappedComponent
            lastUpdated={this.state.lastUpdated}
            {...this.props}
          />
        );
      }
    };
  };
}

Clase SimpleStore

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

Cómo utilizar

En el caso de ganchos:

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

En el caso de HOC:

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

ASEGÚRESE de exportar sus tiendas como un singleton para obtener el beneficio del cambio global de esta manera:

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

Este enfoque es bastante simple y funciona para mí. También trabajo en equipos grandes y uso Redux y MobX y encuentro que también son buenos, pero solo un montón de repeticiones. Personalmente, me gusta mi propio enfoque porque siempre odié mucho código para algo que puede ser simple cuando lo necesitas.

Jason Sebring
fuente
2
Creo que hay un error tipográfico en el ejemplo de la clase tienda en el método de actualización de tener que escribir la primera línea del método de la siguiente manera: this._data = {...this._data, ...newData}.
Norbert
2
Reaccionar desalienta la herencia a favor de la composición. reactjs.org/docs/composition-vs-inheritance.html
Fred
1
Solo me pregunto acerca de la claridad / legibilidad, ¿cómo podría ser mejor this.setState (this.state) que this.forceUpdate ()?
Zoman
17

Entonces, supongo que mi pregunta es: ¿los componentes React deben tener un estado para volver a reproducir? ¿Hay alguna forma de obligar al componente a actualizarse a demanda sin cambiar el estado?

Las otras respuestas han tratado de ilustrar cómo podrías hacerlo, pero el punto es que no deberías . Incluso la solución hacky de cambiar la clave pierde el punto. El poder de React es ceder el control de administrar manualmente cuándo algo debería renderizarse, y en cambio solo preocuparse por cómo algo debería mapearse en las entradas. Luego suministrar flujo de entradas.

Si necesita forzar manualmente el re-renderizado, seguramente no está haciendo algo bien.

Kyle Baker
fuente
@ art-solopov, ¿a qué problema te refieres? ¿Qué documentos los presentan? Y suponiendo que se refiera a objetos anidados en estado, la respuesta es usar una estructura diferente para almacenar su estado. Esa es claramente una forma subóptima de manejar el estado en React. Tampoco debe administrar el estado por separado. Estás perdiendo uno de los mayores beneficios de React si no trabajas con el sistema de gestión estatal. Administrar actualizaciones manualmente es un antipatrón.
Kyle Baker el
Por favor, ¿puedes consultar esta pregunta stackoverflow.com/questions/61277292/… ?
HuLu ViCa
4

Podrías hacerlo de dos maneras:

1. Use el forceUpdate()método:

Hay algunos problemas técnicos que pueden ocurrir al usar el forceUpdate()método. Un ejemplo es que ignora el shouldComponentUpdate()método y volverá a representar la vista independientemente de si shouldComponentUpdate()devuelve falso. Debido a esto, se debe evitar forceUpdate () cuando sea posible.

2. Pasando this.state al setState()método

La siguiente línea de código supera el problema con el ejemplo anterior:

this.setState(this.state);

Realmente, todo lo que está haciendo es sobrescribir el estado actual con el estado actual que desencadena una nueva representación. Todavía no es necesariamente la mejor manera de hacer las cosas, pero supera algunos de los problemas técnicos que puede encontrar utilizando el método forceUpdate ().

kojow7
fuente
2
Dado que setState está agrupado, probablemente sea más seguro hacerlo:this.setState(prevState => prevState);
Mark Galloway,
1
No estoy seguro de por qué eso es un "problema técnico". El nombre del método ('forceUpdate') no podría ser más claro: forzar la actualización siempre.
Zoman
4

Hay algunas formas de volver a procesar su componente:

La solución más simple es usar el método forceUpdate ():

this.forceUpdate()

Una solución más es crear una clave no utilizada en el estado (nonUsedKey) y llamar a la función setState con la actualización de esta nonUsedKey:

this.setState({ nonUsedKey: Date.now() } );

O reescribir todo el estado actual:

this.setState(this.state);

El cambio de utilería también proporciona la recuperación de componentes.

Jackkobec
fuente
3

Para completar, también puede lograr esto en componentes funcionales:

const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
// ...
forceUpdate();

O, como un gancho reutilizable:

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();

Ver: https://stackoverflow.com/a/53215514/2692307

Tenga en cuenta que usar un mecanismo de actualización forzada sigue siendo una mala práctica, ya que va en contra de la mentalidad de reacción, por lo que aún debe evitarse si es posible.

Lukas Bach
fuente
2

Podemos usar this.forceUpdate () como se muestra a continuación.

       class MyComponent extends React.Component {



      handleButtonClick = ()=>{
          this.forceUpdate();
     }


 render() {

   return (
     <div>
      {Math.random()}
        <button  onClick={this.handleButtonClick}>
        Click me
        </button>
     </div>
    )
  }
}

 ReactDOM.render(<MyComponent /> , mountNode);

La parte Element 'Math.random' en el DOM solo se actualiza incluso si usa setState para volver a representar el componente.

Todas las respuestas aquí son correctas, complementando la pregunta para la comprensión ... ya que sabemos que volver a representar un componente sin usar setState ({}) es usando forceUpdate ().

El código anterior se ejecuta con setState como se muestra a continuación.

 class MyComponent extends React.Component {



             handleButtonClick = ()=>{
                this.setState({ });
              }


        render() {
         return (
  <div>
    {Math.random()}
    <button  onClick={this.handleButtonClick}>
      Click me
    </button>
  </div>
)
  }
 }

ReactDOM.render(<MyComponent /> , mountNode);
Dhana
fuente
1

Solo otra respuesta para respaldar la respuesta aceptada :-)

React desalienta el uso de forceUpdate()porque generalmente tienen un enfoque muy "esta es la única forma de hacerlo" hacia la programación funcional. Esto está bien en muchos casos, pero muchos desarrolladores de React vienen con un fondo OO, y con ese enfoque, está perfectamente bien escuchar un objeto observable.

Y si lo hace, probablemente sepa que DEBE volver a renderizar cuando el "fuego" observable, y como tal, DEBE usarlo forceUpdate()y en realidad es un plus que shouldComponentUpdate()NO está involucrado aquí.

Herramientas como MobX, que adopta un enfoque OO, en realidad está haciendo esto debajo de la superficie (en realidad, MobX llama render()directamente)

Plaul
fuente
0

He encontrado que es mejor evitar forceUpdate (). Una forma de forzar el re-render es agregar dependencia de render () en una variable externa temporal y cambiar el valor de esa variable cuando sea necesario.

Aquí hay un ejemplo de código:

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

Llame this.forceChange () cuando necesite forzar el re-render.

raksheetbhat
fuente
0

ES6 - Incluyo un ejemplo que me fue útil:

En una "instrucción if corta" puede pasar una función vacía como esta:

isReady ? ()=>{} : onClick

Este parece ser el enfoque más corto.

()=>{}
MrDoda
fuente
0

forceUpdate(); el método funcionará pero es aconsejable usar setState();

Chiamaka Ikeanyi
fuente
0

Para lograr lo que está describiendo, intente this.forceUpdate ().

API-Andy
fuente
¿Qué hace esta acción?
Dominique
0

Otra manera está llamando setState, y conserva el estado de:

this.setState(prevState=>({...prevState}));

dvvtms
fuente
0

forceUpdate (), pero cada vez que escuché a alguien hablar sobre eso, se le ha seguido y nunca debe usar esto.

Ian
fuente
-1

Puede usar forceUpdate () para ver más detalles ( forceUpdate () ).

Harshit Singhai
fuente
1
Por curiosidad, ¿por qué dejar esta respuesta si durante 4 años la gente ya ha ofrecido eso como una posible solución en este hilo?
Byron Coetsee