Continuando a série de artigos sobre componentes com React, nesse artigo irei mostrar como trabalhar com state (estado) de um componente. Caso você tenha perdido o artigo anterior onde mostro como renderizar e criar seu primeiro componente, segue o link abaixo:
Agora que já temos a nossa lista de tarefas sendo renderizada, precisamos criar o recurso para adicionar novas.
Atualizando JSX
O primeiro passo será criar o input
e button
; no nosso componente. Podemos fazer isso simplesmente mudando o código da função render
:
<input type="text"/> <button>Adicionar</button> <ul> { tarefas.map(tarefa => <li>{ tarefa }</li>) } </ul>
Apenas foi adicionado uma tag input
e outra button
.
Se você fez a alteração, deve ter notado que o componente passou a dar um erro:
Esse erro está acontecendo porque um componente em React deve retornar apenas um filho para a função render
, porém, em nosso exemplo estamos retornando três, sendo eles: input
, button
e ul
.
Para resolver o problema precisamos apenas criar um elemento para encapsular (fazer o wrap) dos demais elementos:
<div> <input type="text"/> <button>Adicionar</button> <ul> { tarefas.map(tarefa => <li>{ tarefa }</li>) } </ul> </div>
Adicionando uma tag div
como mãe de todos os demais elementos, o problema foi resolvido, mas será que eu quero renderizar essa div
no meu DOM (na minha página)? Obviamente não, né. Estou apenas sendo obrigado a utilizá-la para o meu componente conseguir retornar apenas um filho e posteriormente ser renderizado.
Sabendo dessa necessidade, a galera do React criou um componente chamado Fragment, a idéia do Fragment
é realizar o mesmo papel que a div
nesse exemplo, ou seja, encapsular e fazer wrap de demais elementos, porém, ele não é renderizado em nosso DOM.
Para utilizá-lo, importe-o do módulo do react
:
import React, { Component, Fragment } from "react";
E troque a div
por ele:
<Fragment> <input type="text"/> <button>Adicionar</button> <ul> { tarefas.map(tarefa => <li>{ tarefa }</li>) } </ul> </Fragment>
Legal, agora já temos nosso componente sendo renderizado, porém, sem estilo algum (assunto para os próximos artigos):
Até o momento nosso input e button não fazem nada; precisamos agora – de alguma maneira – pegar o valor do input a cada mudança e salvá-lo em algum lugar. Como podemos fazer isso? Para resolver esse tipo de necessidade, o React criou o famoso state.
Mantendo estados
O state serve para guardarmos, atualizarmos e mantermos estados em nossos componentes. Para criá-lo basta apenas adicioná-lo como atributo da classe Tarefas
:
state = { tarefa : "" }
Essa é uma maneira mais nova e recente. Em alguns exemplos você deve ver algo parecido com:
constructor(props) { super(props); this.state = { tarefa : "" }; }
Cria-se um construtor em nosso componente, onde o mesmo recebe como parâmetro as propriedades do componente e chama o construtor da classe mãe através do super
. No caso, a classe Component
, passando as propriedades para ela.
Logo após, cria-se o state
com os valores iniciais.
Fica a seu critério qual maneira mais lhe agrada. Tome cuidado apenas com compatibilidade, talvez seja necessário algum transpiler, como o Babel, por exemplo.
Nosso componente por padrão vai possuir um estado onde contém apenas uma propriedade que seria a tarefa cujo valor é branco.
Agora vamos passar o valor desse estado para nosso input
:
<input type="text" value={ this.state.tarefa }/>
Dessa maneira, já fizemos a passagem de valor do componente para o template
.
Precisamos agora fazer o processo inverso, ou seja, passar o valor do template
para o componente. Para isso, vamos precisar utilizar a função onChange
do input
, onde uma função de handleChange
será passada para ela:
<input onChange={ this.handleChange } type="text" value={ this.state.tarefa }/>
handleChange(event) { this.setState({ tarefa : event.target.value }); }
A função handleChange
simplesmente recebe um event
como parâmetro, onde através dele conseguimos pegar o valor atual do input
. Depois chamamos a função setState
do componente passando um novo estado para ele.
Se você tentou digitar algum valor no input
, deve ter se deparado com um novo erro:
Isso aconteceu porque quando o input
chamou a função handleChange
, o escopo de execução é diferente do componente. Dessa maneira o this
não é o componente, e sim o valor undefined
. Para resolver o problema, uma das possíveis maneiras seria dentro do construtor do componente mudarmos o escopo da função:
constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); }
Dessa maneira, estamos falando que a função handleChange
será igual a uma nova função, cujo o valor seria ela mesmo, porém, com seu escopo mudado para o componente, por isso é passado o .bind(this).
No caso, o this
seria o próprio componente.
Saiba mais
Um event do React, além de conseguir pegar o valor do input
, também possuí outros recursos e funcionalidades; o mesmo trata-se de uma classe chamada SyntheticEvent.
A função setState
, além de atualizar o estado do componente também realiza alguns novos processos no ciclo de vida do mesmo e seus filhos – ela dispara as funções: shouldComponentUpdate
e render
. Dessa maneira o componente é re-renderizado (desde que a função shouldComponentUpdate
retorne true
).
Adicionando novas tarefas
Agora precisamos criar a função do botão. A ideia será a mesma; vamos criar uma função e passá-la para o botão invocar ao ser realizado o click no mesmo:
<button onClick={ this.handleClick }>Adicionar</button>
handleClick() { this.setState({ tarefas : [].concat(this.state.tarefas, this.state.tarefa) }); }
Não se esqueça de mudar o escopo da função para o escopo do componente:
constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.handleClick = this.handleClick.bind(this); }
Observação: repare que foi necessário adicionar mais uma propriedade no estado do componente, sendo ela tarefas
:
state = { tarefa : "", tarefas : [] }
Voltando para o componente e testando, veja que nada aconteceu, isso porque precisamos mudar de onde buscamos as tarefas a serem renderizadas. Precisamos trocar a variável local dentro da função render
para o valor do nosso state
:
render() { return ( <Fragment> <input onChange={ this.handleChange } type="text" value={ this.state.tarefa }/> <button onClick={ this.handleClick }>Adicionar</button> <ul> { this.state.tarefas.map(tarefa => <li>{ tarefa }</li>) } </ul> </Fragment> ); }
Por último, precisamos adicionar um atributo chamado key
para nossos arrays
ou iterators
. É através desse key
que o React consegue saber o que precisa ser mudado em nosso código.
Saiba mais
O atributo key não será renderizado; ele serve apenas para o React realizar o “diff”, sabendo o que saiu ou entrou em nosso array, atualizando apenas aquele pedaço e não o array
todo. Dessa maneira a performance fica muito melhor.
Conclusão
Nesse artigo mostrei como trabalhar com estados em componentes com React. Podemos fazer isso através do state
. Também vimos como atualizar os valores dos estados e pedir para o componente ser renderizado novamente através da função setState
.
O funcionamento final do componente ficou da seguinte maneira:
O projeto feito nesse e no post anterior pode ser encontrado aqui.
Até a próxima!