¿Cómo puedo forzar al componente a volver a renderizar con ganchos en React?

108

Considerando el siguiente ejemplo de ganchos

   import { useState } from 'react';

   function Example() {
       const [count, setCount] = useState(0);

       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>
                  Click me
               </button>
          </div>
        );
     }

Básicamente usamos este método.forceUpdate () para forzar al componente a volver a renderizarse inmediatamente en los componentes de la clase React como el siguiente ejemplo

    class Test extends Component{
        constructor(props){
             super(props);
             this.state = {
                 count:0,
                 count2: 100
             }
             this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component 
        }
        setCount(){
              let count = this.state.count;
                   count = count+1;
              let count2 = this.state.count2;
                   count2 = count2+1;
              this.setState({count});
              this.forceUpdate();
              //before below setState the component will re-render immediately when this.forceUpdate() is called
              this.setState({count2: count
        }

        render(){
              return (<div>
                   <span>Count: {this.state.count}></span>. 
                   <button onClick={this.setCount}></button>
                 </div>
        }
 }

Pero mi consulta es ¿Cómo puedo forzar al componente funcional anterior a volver a renderizar inmediatamente con ganchos?

Hemadri Dasari
fuente
1
¿Puede publicar una versión de su componente original que utilice el this.forceUpdate()? Tal vez haya una forma de lograr lo mismo sin eso.
Jacob
La última línea de setCount está truncada. No está claro cuál es el propósito de setCount en su estado actual.
Estus Flask
Eso es solo una acción después de this.forceUpdate (); Agregué eso solo para explicar sobre this.forceUpdate () en mi pregunta
Hemadri Dasari
Por lo que vale: estaba luchando con esto porque pensé que necesitaba una re-renderización manual, y finalmente me di cuenta de que simplemente necesitaba mover una variable retenida externamente a un gancho de estado y aprovechar la función de configuración, que solucionó todos mis problemas sin una re-renderización. No quiere decir que nunca sea ​​necesario, pero vale la pena echar un tercer y cuarto vistazo para ver si realmente es necesario en su caso de uso específico.
Alexander Nied

Respuestas:

74

Esto es posible con useStateo useReducer, ya que se useStateusa useReducerinternamente :

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

forceUpdateno está diseñado para usarse en circunstancias normales, solo en pruebas u otros casos pendientes. Esta situación se puede abordar de una manera más convencional.

setCountes un ejemplo de uso incorrecto forceUpdate, setStatees asincrónico por razones de rendimiento y no se debe forzar a ser sincrónico solo porque las actualizaciones de estado no se realizaron correctamente. Si un estado se basa en el estado establecido previamente, esto debe hacerse con la función de actualización ,

Si necesita establecer el estado en función del estado anterior, lea sobre el argumento del actualizador a continuación.

<...>

Se garantiza que tanto el estado como los accesorios recibidos por la función de actualización están actualizados. La salida del actualizador se fusiona superficialmente con el estado.

setCount Puede que no sea un ejemplo ilustrativo porque su propósito no está claro, pero este es el caso de la función de actualización:

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}

Esto se traduce 1: 1 a ganchos, con la excepción de que las funciones que se utilizan como devoluciones de llamada deben memorizarse mejor:

   const [state, setState] = useState({ count: 0, count2: 100 });

   const setCount = useCallback(() => {
     setState(({count}) => ({ count: count + 1 }));
     setState(({count2}) => ({ count2: count + 1 }));
     setState(({count}) => ({ count2: count + 1 }));
   }, []);
Matraz Estus
fuente
¿Cómo const forceUpdate = useCallback(() => updateState({}), []);funciona? ¿Incluso fuerza una actualización?
Dávid Molnár
1
@ DávidMolnár useCallbackmemoriza forceUpdate, por lo que se mantiene constante durante la vida útil de los componentes y se puede pasar como un accesorio de forma segura. updateState({})actualiza el estado con un nuevo objeto en cada forceUpdatellamada, esto da como resultado una re-renderización. Entonces sí, fuerza una actualización cuando se llama.
Estus Flask
3
Entonces, la useCallbackparte no es realmente necesaria. Debería funcionar bien sin él.
Dávid Molnár
¿Y no funcionaría updateState(0)porque 0es un tipo de datos primitivo? ¿Debe ser un objeto o también funcionaría updateState([])(es decir, el uso con una matriz)?
Andru
1
@Andru Sí, un estado se actualiza una vez porque 0 === 0. Sí, una matriz funcionará porque también es un objeto. Todo lo que no pase la verificación de igualdad se puede usar, como updateState(Math.random()), o un contador.
Estus Flask
26

Como han mencionado los demás, useStatefunciona (así es como mobx-react-lite implementa las actualizaciones), podría hacer algo similar.

Definir un nuevo gancho, useForceUpdate-

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

y usarlo en un componente -

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

Consulte https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts y https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts

Brian Burns
fuente
2
Por lo que entiendo de los ganchos, es posible que esto no funcione, ya useForceUpdateque devolverá una nueva función cada vez que la función vuelva a renderizarse. Para forceUpdateque funcione cuando se usa en a useEffect, debe regresar useCallback(update)Ver kentcdodds.com/blog/usememo-and-usecallback
Martin Ratinaud
Gracias, @MartinRatinaud - sí, podría causar una pérdida de memoria sin useCallback (?) - arreglado.
Brian Burns
24

Generalmente, puede utilizar cualquier enfoque de manejo de estado que desee para activar una actualización.

Con TypeScript

ejemplo de codesandbox

useState

const forceUpdate: () => void = React.useState()[1].bind(null, {})  // see NOTE below

useReducer

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

como gancho personalizado

Simplemente envuelva el enfoque que prefiera así

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

¿Cómo funciona esto?

" Activar una actualización " significa decirle al motor React que algún valor ha cambiado y que debería volver a procesar su componente.

[, setState]from useState()requiere un parámetro. Nos deshacemos de él atando un objeto nuevo {}.
() => ({})in useReduceres un reductor ficticio que devuelve un objeto nuevo cada vez que se envía una acción.
{} (objeto nuevo) es necesario para que active una actualización cambiando una referencia en el estado.

PD: useStatesolo se envuelve useReducerinternamente. fuente

NOTA: El uso de .bind con useState provoca un cambio en la referencia de función entre las representaciones. Es posible envolverlo dentro de useCallback como ya se explicó aquí , pero entonces no sería un sexy one-liner ™ . La versión Reducer ya mantiene la igualdad de referencia entre los renders. Esto es importante si desea pasar la función forceUpdate en props.

JS simple

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
QWERTY
fuente
14

Alternativa a la respuesta de @ MinhKha:

Puede ser mucho más limpio con useReducer:

const [, forceUpdate] = useReducer(x => x + 1, 0);

Uso: forceUpdate()- limpiador sin params

nulo
fuente
12

Simplemente puede definir el useState así:

const [, forceUpdate] = React.useState(0);

Y uso: forceUpdate(n => !n)

Espero que esto ayude !

Minh Kha
fuente
7
Fallará si se llama a forceUpdate un número par de veces por renderizado.
Izhaki
Sigue incrementando el valor.
Gary
2
Esto es propenso a errores y debe eliminarse o editarse.
Slikts
10

Preferiblemente, solo debe hacer que su componente dependa del estado y los accesorios y funcionará como se espera, pero si realmente necesita una función para obligar al componente a volver a renderizarse, puede usar el useStategancho y llamar a la función cuando sea necesario.

Ejemplo

const { useState, useEffect } = React;

function Foo() {
  const [, forceUpdate] = useState();

  useEffect(() => {
    setTimeout(forceUpdate, 2000);
  }, []);

  return <div>{Date.now()}</div>;
}

ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Tholle
fuente
Ok, pero ¿por qué React ha introducido this.forceUpdate (); en primer lugar cuando el componente se vuelve a renderizar con setState en versiones anteriores?
Hemadri Dasari
1
@ Think-Twice Personalmente nunca lo he usado, y no puedo pensar en un buen caso de uso para él en este momento, pero supongo que es una vía de escape para esos casos de uso realmente especiales. "Normalmente, debe intentar evitar todos los usos de forceUpdate()y solo leer desde this.propsy hacia this.stateadentro render()".
Tholle
1
Convenido. Incluso nunca usé eso en mi experiencia, pero sé cómo funciona, así que solo trato de entender cómo se puede hacer lo mismo en los ganchos
Hemadri Dasari
1
@Tholle and I can't think of a good use case for it right now tengo uno, ¿qué tal si el estado no está controlado por React? No uso Redux, pero supongo que debe tener algún tipo de actualización forzada. Yo personalmente uso proxy para mantener el estado, el componente puede luego verificar los cambios de prop y luego actualizar. Parece que también funciona de manera muy eficiente. Por ejemplo, todos mis componentes son controlados por un proxy, este proxy está respaldado por SessionStorage, por lo que incluso si el usuario actualiza su página web, se mantiene el estado de los menús desplegables, etc. IOW: No uso el estado en absoluto, todo se controla con accesorios.
Keith
8

Solución oficial de React Hooks FAQ para forceUpdate:

const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>

Ejemplo de trabajo

ford04
fuente
6

Código simple

const forceUpdate = React.useReducer(bool => !bool)[1];

Utilizar:

forceUpdate();
GilCarvalhoDev
fuente
5

Puede (ab) usar ganchos normales para forzar una repetición aprovechando el hecho de que React no imprime booleanos en código JSX

// create a hook
const [forceRerender, setForceRerender] = React.useState(true);

// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);

// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
  <div>{forceRerender}</div>
)

Fergie
fuente
1
En este caso, cuando setBoolean cambia dos veces, los niños React.useEffect podrían no reconocer la actualización.
alma
Por lo que tengo entendido, React trata cada actualización booleana como una razón para volver a renderizar la página, incluso si el booleano vuelve a cambiar rápidamente. Dicho esto, React, por supuesto, no es un estándar, y exactamente cómo funciona en este caso no está definido y está sujeto a cambios.
Fergie
1
No lo sé. Por alguna razón, me atrae esta versión. Me hace cosquillas :-). Además de eso, se siente puro. Algo cambió de lo que depende mi JSX, así que me rindo. Su invisibilidad no le resta valor a esa OMI.
Adrian Bartholomew
4

La opción potencial es forzar la actualización solo en el uso de componentes específicos key. La actualización de la clave desencadena una representación del componente (que no se pudo actualizar antes)

Por ejemplo:

const [tableKey, setTableKey] = useState(1);
...

useEffect(() => {
    ...
    setTableKey(tableKey + 1);
}, [tableData]);

...
<DataTable
    key={tableKey}
    data={tableData}/>
Idan
fuente
1
Esta es a menudo la forma más limpia si existe una relación 1: 1 entre un valor de estado y el requisito de volver a renderizar.
jaimefps
1
esta es una solución fácil
Priyanka V
3

Solución de una línea:

const useForceUpdate = () => useState()[1];

useState devuelve un par de valores: el estado actual y una función que lo actualiza - state y setter , aquí estamos usando solo el setter para forzar la re-renderización.

arielhad
fuente
2

Mi variación de forceUpdateno es a través de un countersino más bien a través de un objeto:

// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])

Porque {} !== {}cada vez.

catanfetamina
fuente
¿Qué es useCallback()? De donde vino eso? Ups. Lo veo ahora ...
zipzit
2

Esto representará los componentes dependientes 3 veces (las matrices con elementos iguales no son iguales):

const [msg, setMsg] = useState([""])

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
Janek Olszak
fuente
1
Creo que ni siquiera necesitas poner un elemento en la matriz. Una matriz vacía no es estrictamente igual a otra matriz vacía simplemente por una referencia diferente, al igual que los objetos.
Qwerty
sí, solo quería mostrar eso como una forma de pasar datos
Janek Olszak
1

Para componentes regulares basados ​​en React Class, consulte React Docs para la forceUpdateAPI en esta URL. Los documentos mencionan que:

Normalmente, debe intentar evitar todos los usos de forceUpdate () y solo leer de this.props y this.state en render ()

Sin embargo, también se menciona en los documentos que:

Si su método render () depende de otros datos, puede decirle a React que el componente necesita volver a renderizarse llamando a forceUpdate ().

Entonces, aunque los casos de uso para el uso forceUpdatepueden ser raros, y no lo he usado nunca, sin embargo, lo he visto usado por otros desarrolladores en algunos proyectos corporativos heredados en los que he trabajado.

Por lo tanto, para conocer la funcionalidad equivalente de los componentes funcionales, consulte React Docs for HOOKS en esta URL. Según la URL anterior, se puede utilizar el gancho "useReducer" para proporcionar una forceUpdatefuncionalidad para los componentes funcionales.

A that does not use state or propscontinuación se proporciona un ejemplo de código de trabajo , que también está disponible en CodeSandbox en esta URL

import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  // Use the useRef hook to store a mutable value inside a functional component for the counter
  let countref = useRef(0);

  const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    countref.current++;
    console.log("Count = ", countref.current);
    forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
  }

  return (
    <div className="App">
      <h1> {new Date().toLocaleString()} </h1>
      <h2>You clicked {countref.current} times</h2>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        ClickToUpdateDateAndCount
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

NOTA: En esta URL también se encuentra disponible un enfoque alternativo que utiliza el gancho useState (en lugar de useReducer) .

Alan CS
fuente
1

react-tidytiene un gancho personalizado solo para hacer eso llamado useRefresh:

import React from 'react'
import {useRefresh} from 'react-tidy'

function App() {
  const refresh = useRefresh()
  return (
    <p>
      The time is {new Date()} <button onClick={refresh}>Refresh</button>
    </p>
  )
}

Más información sobre este gancho

Descargo de responsabilidad Soy el autor de esta biblioteca.

webNeat
fuente