da2k / curso-reactjs-ninja

915 stars 322 forks source link

m1#a46 - setState sequenciais #602

Closed fernandomk6 closed 1 year ago

fernandomk6 commented 1 year ago

Duvida

Fala Professor, tudo bem?

Estou com dúvidas no fluxo de execução de 2 ou mais setState que estão sendo executados um após o outro.

Descrição

Na aplicação github app, mostrada no modulo 1 part 2, na aula 46, o senhor mostra como exibir uma mensagem de loading, enquanto, o request do search, não é concluído.

Porém, nesse método handleSearch são executados mais de um setState, e como eu sei que, o setState é assíncrono, eu gostaria que se possível, o senhor me esclarecesse o fluxo de execução, dos setState que ocorrem nesse método.

Usei async/await e fetch, ao invés de thens/catch e jax. Acredito que isso não venha a causar grandes diferenças.

Código

  async handleSearch (e) {
    const keyCode = e.which || e.keyCode
    const enter = 13
    const value = e.target.value

    if (keyCode !== enter) {
      return
    }

    this.setState({ isFetching: true }) // setState 1 loading iniciado
    try {
      const url = this.getGitHubApiUrl(value)
      const userData = await (await fetch(url)).json()

      if (!userData.login) {
        throw new Error('Usuário não encontrado')
      }

      const userInfo = {
        login: userData.login,
        name: userData.name,
        photo: userData.avatar_url,
        link: userData.html_url,
        repos: userData.public_repos,
        following: userData.following,
        followers: userData.followers
      }

      this.setState({ userInfo, repos: [], starred: [] }) // setState 2 fetch concluido
    } catch (error) {
      console.log(error)

      this.setState({ 
        userInfo: null,
        repos: [],
        starred: []
      })
    }

    this.setState({ isFetching: false }) // setState 3 loading encerrado
  }

O que acontece

Esse código ocorre conforme esperado.

Ao pressionar enter, a mensagem de loading acontece (o state foi "passado para frente" alterando a renderização dos filhos). Após alguns segundos, o loading some, e é renderizado os dados do usuário.

Ou seja, existe um "tempo", entre as chamadas dos setState e consequentemente de seus renders.

Esse "tempo" é de fato o que me causa dúvida com relação a natureza assíncrono do setState

Como eu vejo

Eu sei que setState é assíncrono, então eu imagino que, todas as chamadas de setState serão executadas, após toda a leitura da função handleSearch. Imagino a call assim:

Porém, se fosse assim, não haveria um "tempo perceptível" entre as chamadas setState. Tempo entre exibição do loading e exibição dos resultados.

Em resumo

O setState parece ter um comportamento síncrono poís, imediatamente ao setar o state isFetching, a interface responde, e imediatamente ao setar o state userInfo a interface responde.

Como eu até então venho vendo-os como assíncronos eu imaginava que essas execuções ocorrecem imadiatamente uma apos a outra... como se fossem acumulados para serem executados depois numa especie de macro ou micro task...

Desculpa a mensagem longa professor. Apesar do codigo está funcionando, eu preciso ter isso bem claro, pois, é praticamente a base do react o setState e seus efetios.

Desde já agradeço professor.

@fdaciuk

fernandomk6 commented 1 year ago

Só para complementar professor. Na documentação do react é possivel encontrar algo como

Isso significa que, dependendo da implementação, pode ser possível que as duas chamadas a this.setState sejam agrupadas em uma única atualização de estado, em vez de serem aplicadas individualmente.

Isso confunde bastante... de forma "grosseira"... é assíncrono ou não; Ou depende; E se depende depende exatamente de que? Como eu conseguiria "prever" o comportamento, se será assíncrono ou não. Pois isso faz total diferença na hora de implementar uma funcionalidade.

A interface reage instantaneamente apos uma chamada de setState? Como se a função atual fosse pausada. E continuasse apos a renderização...

Ou a renderização proveniente do setState ocorre apenas depois de toda a função ser lida?

Como você pode perceber estou bastante confuso com relação a essas questões e um pouco preocupado por que é um conceito muito importante.

fdaciuk commented 1 year ago

Oi @fernandomk6! Excelente questão, vou detalhar aqui os pontos de dúvida =)

Antes de tudo, sobre essa frase aqui:

Usei async/await e fetch, ao invés de thens/catch e jax. Acredito que isso não venha a causar grandes diferenças.

Não tem problema. Se você sabe como usar o fetch, o resultado final será o mesmo =)


como funciona o setState por baixo dos panos

Sobre o setState: sim, ele é sempre assíncrono! Mas o que isso significa, de forma prática?

O setState ser assíncrono não significa que a execução dele acontece "depois" de tudo. Na verdade, toda função que é assíncrona é executada sempre no momento em que ela é chamada. O que normalmente nós fazemos é passar para essa função uma outra função via argumento que, essa sim, será executada somente quando a ação assíncrona esperada finalizar.

Então, ao executar o this.setState(), você está dizendo para o React que, naquele momento, você quer que o componente seja atualizado, mas com os novos dados, com os valores que você passou via argumento para o setState.

O que é assíncrono nesse caso é, de fato, a atualização do estado: quando o setState é executado, o React vai "re-renderizar" o componente. Se você estiver usando uma classe, a re-renderização é basicamente a chamada sequencial dos métodos render (com o this.state já atualizado) e então é chamado o lifecicle componentDidUpdate.

Mas a sua função anterior, que chamou o setState continua rodando, ela ainda não terminou de executar tudo o que tinha para executar, por isso que, ao chamar o setState, você consegue ver o resultado "na hora".

Como a sua função continua executando, ela ainda não consegue acessar os dados novos do estado via this.state, apesar do componente já ter sido re-renderizado, pois para isso acontecer, a função teria que ser executada de novo.

É aí que está a parte "assíncrona" do setState: se o this.state.isFetching era false quando você executou a função, e dentro da função você executa um this.setState({ isFetching: true }), logo após esse setState, na mesma função, você não vai ver o this.state.isFetching como true. Ele vai continuar como false, exatamente porque essa atualização do estado acontece de forma assíncrona.

Então o fluxo de execução da sua função vai continuar normalmente: desde que você não tente acessar o this.state dentro dessa função, achando que o estado vai estar atualizado nesse objeto, tudo vai acontecer como deveria.

Em resumo: a interface responde o mais rápido possível à chamada do setState (de forma assíncrona, ou seja: o seu código não para de ser executado quando você chama o setState), mas dentro da função que você chamou o setState, você nunca vai ter acesso ao estado atualizado via this.state.

Deu pra entender a ideia?


Chamadas agrupadas do setState

Esse comportamento que a documentação está ser referindo, é quando você chama o setState mais de uma vez, de forma seguida. Exemplo:

this.setState({ isFetching: true })
this.setState({ isFetching: false })
this.setState({ isFetching: true })
this.setState({ isFetching: false })
this.setState({ isFetching: true })

Se você fizer algo nesse sentido, o React vai fazer o componente re-renderizar uma única vez, já que a chamada do setState é assíncrona, então é possível saber que uma função foi executada mais de uma vez em período curto de tempo.

Se for esse o caso, o React não vai executar 5 re-renders, mas somente 1, com o valor mais recente para o isFetching.

Não é o caso do que estamos fazendo: nós chamamos o setState, e logo após fazemos um request que vai demorar um pouco para ser executado. O próximo setState só vai acontecer quando o request terminar, então a re-renderização vai acontecer como esperado =)


Ficou tudo claro? Se ainda restou qualquer dúvida, fique muito à vontade para perguntar, porque essa parte é muito importante sim :D

fernandomk6 commented 1 year ago

Ficou uma dúvida sim professor.

A dúvida que ainda me resta é a seguinte: O this.setState chama, de forma síncrona o método render? Em que momento o método render encadeado pelo this.setState será de fato executado, durante a função? depois dela... ou em paralelo?

handleClick (e) {

this.setState({ aState: e.target.value })
// this.render() é invocado pelo setState de forma síncrona agora

// ...O que está aqui a baixo será executado após o render ser executado?
}

De forma mais resumida, o render que é encadeado pelo setState, pode ser, executado, durante a execução da função atual? Como o exemplo acima, setState(); this.render() ...resto do código

Eu entendo bem o conceito de callbacks e funções sendo executadas de forma assíncrona, mas nesse exemplo em especifico, parece que tanto o render como a função atual, são executadas em paralelo, o que não é possível pois o JS executa uma coisa de cada vez.

fdaciuk commented 1 year ago

Ou seja, não adianta fazer chamadas "acumuladas" pois o state ainda não foi atualizado.

Na verdade dá pra fazer, eu mostro isso com mais detalhes no módulo 2, e depois no módulo 4 também, com hooks =)

Mas em resumo, é só passar uma função para o setState, ao invés do objeto que você vai atualizar. A função vai conter o estado atualizado, ainda que o componente não tenha re-renderizado =)

A dúvida que ainda me resta é a seguinte: O this.setState chama, de forma síncrona o método render?

Sim!

Em que momento o método render encadeado pelo this.setState será de fato executado, durante a função? depois dela... ou em paralelo?

Isso não vai fazer diferença para o seu código, pois você só vai usar o this.setState quando quiser que haja alguma mudança na sua view (que é renderizada pelo retorno do método render.

Se você quiser ter uma ideia melhor de como isso funciona por baixo dos panos, eu fiz um vídeo mostrando como funciona o hook useState. A ideia é basicamente a mesma com classe: a diferença é que, ao invés de re-executar a função, o React vai re-executar só o método render da classe. Dá uma olhada no vídeo e me diga se essa parte fica mais clara:

https://www.youtube.com/watch?v=yb-fBApqWSw

Eu entendo bem o conceito de callbacks e funções sendo executadas de forma assíncrona, mas nesse exemplo em especifico, parece que tanto o render como a função atual, são executadas em paralelo, o que não é possível pois o JS executa uma coisa de cada vez.

Sim, o JS vai executar uma instrução por vez. E com o this.setState não é diferente: quando você executa essa função, o que for síncrono dentro dessa função vai ser executado "na mesma hora". Depois, o que é assíncrono (atualização do estado e chamada do render) vai ser "agendado" para executar no futuro (event loop), e o próximo código continua rodando.

No caso do seu exemplo, o próximo código é um request, que é assíncrono. Como o request é assíncrono, a execução dele também vai para o event loop e o restante do código continua rodando (aqui já abre um espaço para executar a atualização do estado e a execução do render, chamados pelo setState anteriormente).

Mas assim: isso não deveria ser uma preocupação, pois isso acontece por baixo dos panos, e você não tem controle sobre essa execução (e nem deveria).

O que você precisa saber mesmo é como todo o processo ocorre. Por outro lado, eu entendo que a dúvida de como isso funciona por baixo dos panos pode trazer um pouco de ansiedade, por isso eu gravei o vídeo que eu passei ali em cima.

Assista e me diga se ainda ficou alguma dúvida, ok? :D

fernandomk6 commented 1 year ago

O que você precisa saber mesmo é como todo o processo ocorre.

É exatamente isso professor, eu só quero pode entender claramente a ordem das execuções pois acredito que isso seja muito importante ao estar desenvolvendo aplicações.

Eu estou fazendo em paralelo algumas aplicações simples e lendo a doc do react, e estou vendo que é comum um componente manipular vários estados e muitas vezes esses estados são atualizados juntos. E a parte assíncrona ainda não estava muito clara para mim.

Mas você conseguiu tirar a minha dúvida completamente com essa última explicação.

Era como eu imaginava, porém, eu não estava me atentando ao fato do request que eu fiz, também ser assíncrono, e ele ficou entre os 2 setState. Dessa forma, foi feito duas renderização ao invés de uma.

Caso ficassem juntos, o react faria apenas um render para otimizar.

Irei sim ver seu vídeo professor, mas já me ajudou muito com essas informações. Acredito que o vídeo irá esclarecer mais.

Obrigado.

fdaciuk commented 1 year ago

Massa @fernandomk6! Que bom que ficou claro :D Qualquer dúvida, fique muito à vontade para mandar aqui =)