Você pode forçar um componente React a ser renderizado novamente sem chamar setState?

688

Eu tenho um objeto observável externo (ao componente) no qual desejo escutar alterações. Quando o objeto é atualizado, ele emite eventos de alteração e desejo renderizar novamente o componente quando qualquer alteração for detectada.

Com um nível superior, React.renderisso foi possível, mas dentro de um componente não funciona (o que faz algum sentido, pois o rendermétodo retorna um objeto).

Aqui está um exemplo 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>
    )
  }
}

Clicar no botão chama internamente this.render(), mas não é isso que realmente faz a renderização (você pode ver isso em ação porque o texto criado por {Math.random()}não muda). No entanto, se eu simplesmente ligar em this.setState()vez de this.render(), funciona bem.

Portanto, acho que minha pergunta é: os componentes do React precisam ter um estado para serem renderizados novamente? Existe uma maneira de forçar o componente a atualizar sob demanda sem alterar o estado?

Philip Walton
fonte
7
a resposta aceita diz que this.forceUpdate()é a solução certa, enquanto o restante de todas as respostas e vários comentários são contra o uso forceUpdate(). Será bom dizer que a pergunta ainda não obteve uma solução / resposta adequada?
22417 adi
6
A resposta excluída respondeu à minha pergunta na época. É é tecnicamente a resposta que eu estava procurando e ainda acho a resposta certa. Acho que as outras respostas são boas informações suplementares para as pessoas com a mesma pergunta.
Philip Walton
Interessante notar é que você NÃO PRECISA DE QUALQUER COISA NO ESTADO, além de inicializá-lo em um objeto simples e, em seguida, chamar this.setState ({}) apenas aciona uma nova renderização. Reagir é ótimo, mas também estranho às vezes. Portanto, você pode fazer um loop direto nos dados das lojas quando acionar uma alteração sem necessidade de canalização extra ou se preocupar com os dados por instância do componente.
Jason Sebring
No geral, eu diria que sim. Se você estiver usando a atualização forçada, é para atualizar componentes nos quais eles podem depender de alterações fora do gerenciamento de estado do seu aplicativo. Não consigo pensar em um bom exemplo disso. Útil para saber embora.
Daniel

Respostas:

765

No seu componente, você pode ligar this.forceUpdate()para forçar uma nova renderização.

Documentação: https://facebook.github.io/react/docs/component-api.html

Crob
fonte
168
Outra maneira é this.setState (this.state);
kar
25
O uso do forceupdate não é recomendado, consulte a última resposta.
Varand Pezeshkian 04/04
42
Embora this.forceUpdate()não seja uma solução muito boa para o problema dos solicitantes, é a resposta correta para a pergunta colocada no título. Embora geralmente deva ser evitado, há situações em que forceUpdate é uma boa solução.
ArneHugo 24/05
8
@kar Na verdade, lógica adicional pode ser implementada para shouldComponentUpdate()tornar sua solução inútil.
Qwerty
4
Tamanho máximo da pilha de chamadas excedido
IntoTheDeep 24/08/16
326

forceUpdatedeve ser evitado porque se desvia de uma mentalidade de reagir. Os documentos do React citam um exemplo de quando forceUpdatepode ser usado:

Por padrão, quando o estado ou os props do seu componente são alterados, ele será renderizado novamente. No entanto, se estes forem alterados implicitamente (por exemplo: dados profundos em um objeto são alterados sem alterar o objeto em si) ou se o método render () depende de outros dados, você pode dizer ao React que ele precisa executar novamente o render () chamando forçar atualização().

No entanto, gostaria de propor a idéia de que mesmo com objetos profundamente aninhados, forceUpdateé desnecessário. Ao usar uma fonte de dados imutável, as alterações de rastreamento ficam baratas; uma mudança sempre resultará em um novo objeto, portanto, precisamos apenas verificar se a referência ao objeto foi alterada. Você pode usar a biblioteca JS imutável para implementar objetos de dados imutáveis ​​em seu aplicativo.

Normalmente você deve tentar evitar todos os usos de forceUpdate () e ler apenas this.props e this.state em render (). Isso torna seu componente "puro" e seu aplicativo muito mais simples e mais eficiente. forçar atualização()

Alterar a chave do elemento que você deseja renderizar novamente funcionará. Defina o suporte de chave em seu elemento via estado e, em seguida, quando desejar atualizar o estado definido para ter uma nova chave.

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

Em seguida, ocorre uma alteração e você redefine a chave

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

Quero observar que isso substituirá o elemento em que a chave está sendo alterada. Um exemplo de onde isso pode ser útil é quando você tem um campo de entrada de arquivo que deseja redefinir após o upload de uma imagem.

Embora a verdadeira resposta à pergunta do OP fosse forceUpdate(), achei esta solução útil em diferentes situações. Também quero observar que, se você estiver usando, forceUpdatepoderá revisar seu código e verificar se há outra maneira de fazer as coisas.

NOTA 1-9-2019:

O acima (alterar a chave) substituirá completamente o elemento. Se você estiver atualizando a chave para fazer as alterações, provavelmente há um problema em outro lugar no seu código. O uso Math.random()da chave recriará o elemento a cada renderização. Eu NÃO recomendaria atualizar a chave assim, pois o React usa a chave para determinar a melhor maneira de renderizar novamente as coisas.

pizzarob
fonte
16
Eu ficaria interessado em saber por que isso teve um voto negativo? Eu tenho batido minha cabeça tentando fazer com que os leitores de tela respondessem a alertas de ária e essa é a única técnica que eu achei que funciona de maneira confiável. Se houver algo em sua interface do usuário que gere o mesmo alerta sempre que for clicado, por padrão, o react não renderiza novamente o DOM, para que o leitor de tela não anuncie a alteração. Definir uma chave exclusiva faz com que funcione. Talvez o voto negativo tenha sido porque ainda envolve a definição do estado. Mas forçar uma nova renderização para DOM definindo a chave é de ouro!
Martin Bayly
2
Gostei muito desta resposta porque - 1: o uso de forceupdate () é desencorajado e 2: dessa maneira é muito útil para componentes de terceiros que você instalou via npm, significando que não são seus próprios componentes. Por exemplo, React-redimensionável-e-móvel é um componente de terceiros que eu uso, mas não está reagindo ao meu componente setstate. Esta é uma ótima solução.
Varand Pezeshkian 04/04
80
Este é um hack e um abuso de key. Primeiro, sua intenção não é clara. Um leitor do seu código terá que trabalhar duro para entender por que você está usando keydessa maneira. Segundo, isso não é mais puro que forceUpdate. Reagir "Puro" significa que a apresentação visual do seu componente é 100% dependente de seu estado; portanto, se você alterar qualquer estado, ele será atualizado. Se, no entanto, você tiver alguns objetos profundamente aninhados (o cenário em que os forceUpdatedocumentos citam um motivo para usá-lo), o uso forceUpdatedeixa isso claro. Terceiro, Math.random()é ... aleatório. Teoricamente, poderia gerar o mesmo número aleatório.
atomkirk
8
@xtraSimplicity Não posso concordar que isso esteja em conformidade com a maneira React de fazer as coisas. forceUpdateé a maneira React de fazer isso.
Rob Grant
3
@ Robert Grant, olhando para trás, eu concordo. A complexidade adicionada realmente não vale a pena.
XtraSimplicity
80

Na verdade, forceUpdate()é a única solução correta, pois setState()pode não acionar uma nova renderização se lógica adicional for implementada shouldComponentUpdate()ou quando ela simplesmente retornar false.

forçar atualização()

A chamada forceUpdate()fará com render()que o componente seja chamado, pulando shouldComponentUpdate(). Mais...

setState ()

setState()sempre acionará uma nova renderização, a menos que a lógica de renderização condicional seja implementada shouldComponentUpdate(). Mais...


forceUpdate() pode ser chamado de dentro do seu componente this.forceUpdate()


Ganchos: como forçar o componente a renderizar novamente com ganchos no React?


BTW: Você está mudando de estado ou suas propriedades aninhadas não se propagam?

Qwerty
fonte
1
Uma coisa interessante a se notar é que assingments estado fora do setState como this.state.foo = 'bar' não vai acionar o método render ciclo de vida
lfender6445
4
@ lfender6445 Atribuir estado diretamente com this.statefora do construtor também deve gerar um erro. Pode não ter feito isso em 2016; mas tenho certeza de que faz isso agora.
Tyler
Eu adicionei alguns links para contornar atribuições diretas de estado.
Qwerty
36

Evitei forceUpdatefazendo o seguinte

MANEIRA ERRADA : não use o índice como chave

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

MANEIRA CORRETA : Use a identificação de dados como chave, pode ser algum guia, etc.

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

portanto, ao fazer esse aprimoramento de código, seu componente será ÚNICO e renderizará naturalmente

vijay
fonte
this.state.rows.map((item) => <MyComponent item={item} key={item.id} /> )
Tal
Muito obrigado por me levar na direção certa. Usar o índice como chave foi realmente o meu problema.
RoiEX 30/04/19
Para justificar meu voto negativo, apesar de ser uma boa prática usar essa abordagem, essa resposta não está relacionada à pergunta.
micnic
34

Quando você deseja que dois componentes do React se comuniquem, que não estão vinculados a um relacionamento (pai-filho), é recomendável usar o Flux ou arquiteturas similares.

O que você deseja fazer é ouvir as alterações do armazenamento de componentes observáveis , que mantém o modelo e sua interface, e salvar os dados que fazem com que a renderização seja alterada como stateem MyComponent. Quando a loja envia os novos dados, você altera o estado do seu componente, que aciona automaticamente a renderização.

Normalmente você deve tentar evitar o uso forceUpdate(). A partir da documentação:

Normalmente você deve tentar evitar todos os usos de forceUpdate () e ler apenas this.props e this.state em render (). Isso torna seu aplicativo muito mais simples e mais eficiente

gcedo
fonte
2
Qual é o status da memória de um estado do componente? Se eu tiver cinco componentes em uma página e todos eles estiverem ouvindo um único armazenamento, tenho os dados cinco vezes na memória ou são referências? E todos esses ouvintes não somam rápido? Por que é melhor "escorregar" do que apenas passar dados para o seu destino?
AJFarkas
5
Na verdade, as recomendações são passar os dados da loja para os componentes do componente e usar apenas o estado do componente para coisas como o estado de rolagem e outras coisas menores específicas da interface do usuário. Se você usar o redux (eu recomendo), poderá usar a função connectreact-redux para mapear automaticamente o estado da loja para os componentes, sempre que necessário, com base na função de mapeamento fornecida.
Stijn de Witt
1
@AJFark, como o estado seria atribuído aos dados de uma loja, que é apenas um ponteiro para ele de qualquer maneira, para que a memória não seja um problema, a menos que você esteja clonando.
Jason Sebring
4
"forceUpdate () deve ser sempre evitado" está incorreto. A documentação diz claramente: "Normalmente você deve tentar evitar todos os usos de forceUpdate ()". Existem casos de uso válidos deforceUpdate
AJP
2
@ AJJ, você está absolutamente certo, isso foi um viés pessoal falando :) Eu editei a resposta.
Gcedo
18

use ganchos ou HOC faça a sua escolha

Usando ganchos ou o padrão HOC (componente de ordem superior) , você pode ter atualizações automáticas quando suas lojas mudarem. Essa é uma abordagem muito leve, sem uma estrutura.

useStore Hooks para lidar com as atualizações da loja

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;
}

atualizações de armazenamento de manipulador do withStores HOC

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}
          />
        );
      }
    };
  };
}

Classe 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;
  }
}

Como usar

No caso de ganchos:

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

No caso do 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);

Certifique-se de exportar suas lojas como um singleton para obter o benefício da mudança global da seguinte forma:

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};

Essa abordagem é bastante simples e funciona para mim. Também trabalho em grandes equipes, uso o Redux e o MobX e acho que eles também são bons, mas com bastante clichê. Pessoalmente, gosto da minha própria abordagem, porque sempre odiei muito código para algo que pode ser simples quando você precisa.

Jason Sebring
fonte
2
Acho que há um erro de digitação no seu exemplo classe loja no método de actualização ter que escrever a primeira linha do método da seguinte forma: this._data = {...this._data, ...newData}.
Norbert
2
Reagir desencoraja a herança em favor da composição. reactjs.org/docs/composition-vs-inheritance.html
Fred
1
Apenas querendo saber sobre clareza / legibilidade, como poderia this.setState (this.state) ser melhor que this.forceUpdate ()?
Zoman 13/02/19
17

Acho que minha pergunta é: os componentes do React precisam ter um estado para serem renderizados novamente? Existe uma maneira de forçar o componente a atualizar sob demanda sem alterar o estado?

As outras respostas tentaram ilustrar como você poderia, mas o ponto é que você não deveria . Até a solução hacky de mudar a chave erra o alvo. O poder do React é abrir mão do controle de gerenciar manualmente quando algo deve renderizar e, em vez disso, apenas se preocupar com o modo como algo deve mapear as entradas. Em seguida, forneça o fluxo de insumos.

Se você precisar forçar a re-renderização manualmente, quase certamente não está fazendo algo certo.

Kyle Baker
fonte
@ art-solopov, a que problema você está se referindo? Quais documentos os apresentam? E supondo que você esteja se referindo a objetos aninhados no estado, a resposta é usar uma estrutura diferente para armazenar seu estado. Essa é claramente uma maneira subótima de lidar com o estado no React. Você também não deve gerenciar o estado separadamente. Você está perdendo um dos maiores benefícios do React se não trabalhar com o sistema de gerenciamento de estado. Gerenciar atualizações manualmente é um antipadrão.
Kyle Baker
Por favor, você pode verificar esta pergunta stackoverflow.com/questions/61277292/… ?
HuLu ViCa 17/04
4

Você pode fazer isso de duas maneiras:

1. Use o forceUpdate()método:

Existem algumas falhas que podem ocorrer ao usar o forceUpdate()método. Um exemplo é que ele ignora o shouldComponentUpdate()método e renderiza novamente a exibição, independentemente de o shouldComponentUpdate()retorno ser falso. Por esse motivo, o uso de forceUpdate () deve ser evitado quando possível.

2. Passando this.state para o setState()método

A seguinte linha de código supera o problema com o exemplo anterior:

this.setState(this.state);

Realmente, tudo o que isto está fazendo é sobrescrever o estado atual pelo estado atual que aciona uma nova renderização. Essa ainda não é necessariamente a melhor maneira de fazer as coisas, mas supera algumas das falhas que você pode encontrar usando o método forceUpdate ().

kojow7
fonte
2
Desde setState é batched é provavelmente mais seguro para fazer:this.setState(prevState => prevState);
Mark Galloway
1
Não sei ao certo por que isso é uma "falha". O nome do método ('forceUpdate') não pôde ser mais claro: force a atualização sempre.
Zoman 13/02/19
4

Existem algumas maneiras de renderizar novamente seu componente:

A solução mais simples é usar o método forceUpdate ():

this.forceUpdate()

Mais uma solução é criar uma chave não usada no estado (nonUsedKey) e chamar a função setState com a atualização dessa nonUsedKey:

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

Ou reescreva todo o estado atual:

this.setState(this.state);

A alteração de adereços também fornece a renderização de componentes novamente.

Jackkobec
fonte
3

Para garantir a integridade, você também pode conseguir isso em componentes funcionais:

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

Ou, como um gancho reutilizável:

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

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

Observe que o uso de um mecanismo de atualização de força ainda é uma prática ruim, pois contraria a mentalidade de reação, portanto, ainda deve ser evitado, se possível.

Lukas Bach
fonte
2

Podemos usar this.forceUpdate () como abaixo.

       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);

A parte do elemento 'Math.random' no DOM só é atualizada mesmo se você usar o setState para renderizar novamente o componente.

Todas as respostas aqui estão corretas, complementando a questão da compreensão ... como sabemos re-renderizar um componente sem usar o setState ({}), é usando o forceUpdate ().

O código acima é executado com setState como abaixo.

 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
fonte
1

Apenas mais uma resposta para fazer backup da resposta aceita :-)

React desencoraja o uso de, forceUpdate()porque eles geralmente têm uma abordagem muito "esta é a única maneira de fazê-lo" em relação à programação funcional. Isso é bom em muitos casos, mas muitos desenvolvedores do React vêm com um background OO e, com essa abordagem, é perfeitamente aceitável ouvir um objeto observável.

E se o fizer, você provavelmente sabe que DEVE renderizar novamente quando o observável "disparar" e, portanto, você DEVE usar forceUpdate()e é realmente uma vantagem que shouldComponentUpdate()NÃO está envolvida aqui.

Ferramentas como MobX, que usa uma abordagem OO, estão fazendo isso na superfície (na verdade, o MobX chama render()diretamente)

Plaul
fonte
0

Eu achei melhor evitar forceUpdate (). Uma maneira de forçar a nova renderização é adicionar a dependência de render () a uma variável externa temporária e alterar o valor dessa variável conforme e quando necessário.

Aqui está um exemplo 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>
      )
   }
}

Chame this.forceChange () quando precisar forçar a nova renderização.

raksheetbhat
fonte
0

ES6 - Estou incluindo um exemplo que foi útil para mim:

Em uma "declaração curta", você pode passar uma função vazia como esta:

isReady ? ()=>{} : onClick

Esta parece ser a abordagem mais curta.

()=>{}
MrDoda
fonte
0

forceUpdate(); método funcionará, mas é aconselhável usar setState();

Chiamaka Ikeanyi
fonte
0

Para realizar o que você está descrevendo, tente this.forceUpdate ().

API-Andy
fonte
O que essa ação faz?
Dominique
0

Outra maneira está chamando setState, e preservar estado:

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

dvvtms
fonte
0

forceUpdate (), mas toda vez que ouvi alguém falar sobre isso, foi seguido por você nunca deve usar isso.

Ian
fonte
-1

Você pode usar forceUpdate () para obter mais detalhes, verifique ( forceUpdate () ).

Harshit Singhai
fonte
1
Por curiosidade, por que deixar essa resposta se há 4 anos as pessoas já oferecem isso como uma possível solução nesse segmento?
Byron Coetsee 17/04