¿Cómo puedo forzar que un componente se vuelva a renderizar con ganchos en React?
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 el método this.forceUpdate() para forzar que el componente se vuelva a renderizar 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 que el componente funcional anterior se vuelva a renderizar inmediatamente con ganchos?
Esto es posible con useState
o useReducer
, ya que useState
se usa useReducer
internamente :
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate
no está diseñado para usarse en circunstancias normales, solo en pruebas u otros casos excepcionales. Esta situación puede abordarse de una manera más convencional.
setCount
es un ejemplo de uso incorrecto forceUpdate
, setState
es asincrónico por razones de rendimiento y no se debe forzar a que sea sincrónico solo porque las actualizaciones de estado no se realizaron correctamente. Si un estado depende de un estado establecido previamente, esto debe hacerse con la función de actualización .
Si necesita configurar el estado según el 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 estará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 es mejor memorizar las funciones que se utilizan como devoluciones de llamada:
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 }));
}, []);
Solución oficial de preguntas frecuentes de React HooksforceUpdate
para :
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>
Ejemplo de trabajo
Mostrar fragmento de código
Generalmente, puede utilizar cualquier enfoque de manejo de estado que desee para activar una actualización.
Con mecanografiado
ejemplo de codesandbox
usoEstado
const forceUpdate: () => void = React.useState({})[1].bind(null, {}) // see NOTE below
useReducer (recomendado)
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
como gancho personalizado
Simplemente envuelve el enfoque que prefieras de esta manera
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 debe volver a representar su componente.
[, setState]
from useState()
requiere un parámetro. Nos deshacemos de él uniendo un objeto nuevo {}
.
() => ({})
in useReducer
es 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: useState
simplemente se ajusta useReducer
internamente, así que use el reductor para reducir la complejidad. fuente
NOTA: Inestabilidad referencial
El uso .bind
de with useState
provoca un cambio en la referencia de función entre renderizados.
Es posible envolverlo por dentro useCallback
como ya se explicó en esta respuesta aquí , pero entonces no sería una frase sexy™ . La versión Reducer ya mantiene la igualdad de referencia (estabilidad) entre renderizados. Esto es importante si desea pasar la función forceUpdate en accesorios a otro componente.
JS simple
const forceUpdate = React.useState({})[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
Como han mencionado los demás, useState
funciona: 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