10 obstáculos frequentes encontrados pelos novos tripulantes do React

React está revolucionando o universo front-end, devido ao seu funcionamento, a forma como codamos através dele e o ecossistema que está se formando ao seu redor. Aqui vai um compilado de dificuldades comuns para aqueles que estão realizando testes, ingressando ou mesmo já tem uma boa experiência nesta formidável lib front-end

Rafael Maruta
React Brasil
Published in
10 min readMar 13, 2018

--

É no passo a passo que chegaremos lá!

Iae galera! Belezinha? ^^’

Meu novo artigo também é consequente à minha jornada para ajudar os devs que estão começando com React. Fiz um compilado sobre problemas comuns que travam o desenvolvimento daqueles que estão aprendendo, que eu já passei ou que já trouxeram até mim. Acredito que até devs experientes em React encontrarão dicas úteis aqui.

Neste artigo evitarei tratar de:

  • Problemas resultantes de configuração ou falta de configuração do Webpack;
  • Problemas mais voltados ao JavaScript do que especificamente sobre React;
  • Problemas relacionados a qualquer outra tecnologia relacionada ao Ecossistema do React.

React e as novas features do EcmaScript andam de mãos dadas, portanto estudando bem em paralelo estas features amenizará bem a curva de aprendizado ao estudar React. Vamos lá!

1. Nomes de atributos HTML em elementos JSX

Algumas palavras que são atributos HTML são palavras reservadas do JavaScript. Quando escrevemos em JSX na verdade estamos escrevendo com uma sintaxe especial dentro do JavaScript apenas para ficar mais fácil de compreender. Palavras comuns a atributos HTML e palavras reservadas do JavaScript como for e class devem ser correspondentemente substituídas no JSX por htmlFor e className. Por exemplo:

import React, { Component } from 'react'class InputRow extends Component {
render () {
return (
<div className=’input-row’>
<label htmlFor=’input-name’>My Input</label>
<input name=’input-name' id=’input-name' type=’checkbox’ />
</div>
)
}
}

Para uma informação mais completa, por favor consulte a documentação oficial.

2. Passar props para componentes distantes

Props são parâmetros com valores que queremos transmitir de um elemento pai para um elemento filho. Quando queremos descer as props até 2 níveis (nível de elemento neto) é aceitável, mas a partir disso começa a nos incomodar. Passar as props através de tantos componentes quando estes intermediários nem sequer farão uso destas props significa sujar o código. Diversas soluções com centralização de estados foram criadas para solucionar este tipo de problema, desde implementações simples como a do MobX e até complexas como a do Redux (escrevi um tutorial sobre ele, clique aqui para ver), porém ambas as soluções criam algo que talvez sejam demais para uma simples necessidade de querer passar as props unidirecionalmente de cima para baixo através de vários níveis.

A Context API é uma API nativa do React criada para solucionar este problema usando o Provider Pattern, mas foi considerada instável, sujeita a ser deprecada devido a alguns problemas, porém foi anunciado que na versão 16.3 ela será oficial e estável! E a implementação inclusive ficou mais simples. Agora é só alegria! =D

3. Esquecer de envolver o retorno de elemento JSX com parênteses

Isto é um erro que algumas vezes eu cometi no começo e vejo com frequência outros devs fazerem quando estão começando. Na verdade este problema é originalmente de JavaScript, mas escrito dentro de JSX. Veja ambos os exemplos abaixo:

const MyComponent = () => {
<p>My Component!</p>
}
const MyMapComponent = ({ list }) => {
<ul>
list.map(item => {
<li>
<a href={list.url} title={list.title}>
{list.title}
</a>
</li>
})
</ul>
}

Ambos os componentes acima, se invocados, não printarão corretamente seu conteúdo. Consegue identificar o porquê?

As funções precisam retornar os elementos para que sejam renderizados, porém ao invés de parênteses, há chaves no lugar. Em vez de criarmos um agrupamento de retorno (por este ser em múltiplas linhas, feature do ES6), estamos criando um novo bloco de código, sem retorno algum.

Conseguiu identificar um outro problema no segundo exemplo? É algo que é frequentemente esquecido: quando há o retorno de uma lista através do map, é necessário informarmos uma prop key no elemento mais elevado do seu retorno para que o algoritmo de reconciliação do React consiga diferenciar cada nó e otimizar a performance. É importante que esta chave seja única.

Segue abaixo o exemplo com as correções negritadas:

const MyComponent = () => (
<p>My Component!</p>
)
const MyMapComponent = ({ list }) => (
<ul>
list.map(item => (
<li key={item.title}>
<a href={list.url} title={list.title}>
{list.title}
</a>
</li>
))
</ul>
)

4. Falta de PropTypes

PropTypes reduzem a quantidade de bugs que o componente pode dar, pois elas limitam o tipo do dado da prop ao que você definir. Por exemplo: caso seja passada uma string para um componente que recebe uma prop para fazer cálculo de multiplicação, ele renderizaria NaN, o que não seria nada bom, correto?

Além de problemas visuais pequenos, há problemas que podem acarretar um erro grave em toda a aplicação. Imagine se um componente que grava valores em um banco de dados, recebe ao invés de uma string, um objeto ou uma função? Isto poderia acarretar um erro na execução do código, disparando um erro, ou até algo mais grave como gravar a string do objeto ou da função no banco de dados, causando sérios problemas e uma enorme dor de cabeça para corrigir.

É possível especificar diversos tipos de dados, como string, objetos, números, funções etc. Se a prop puder ser um array com elementos de determinado tipo, podemos usar o arrayOf:

myOwnList: PropTypes.arrayOf(PropTypes.number)

Caso a prop puder variar entre determinados tipos, podemos usar o oneOfType:

myDearData: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
])

Se for um objeto que só pode ter determinadas propriedades, cada uma com determinados tipos, podemos usar o shape:

myBeautifulText: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})

Se uma prop é opcional, devemos informar a sua defaultProp, que é o seu valor padrão que será assumido caso a prop não seja informada pelo seu componente pai:

newPerson.defaultProps = {
name: 'Stranger'
}
newPerson.propTypes = {
name: PropTypes.string
}

Caso a prop seja obrigatória, podemos usar o isRequired:

funcToNotBooooom: PropTypes.func.isRequired

Observação: caso a prop obrigatória não seja passada, será disparado um warning no console, e não um erro.

Como as PropTypes são muito próximas à tipagem estática, é possível substituí-las pelo Flow, que é uma solução mais completa de tipagem de dados criada pelo Facebook. E agora temos também o novato Reason, também criação do Facebook, que é praticamente uma nova linguagem tipada que usa o ecossistema OCaml.

5. Uso desnecessário do método bind

O bind é um método do JavaScript que serve para definirmos qual será o contexto de uma função sem executá-la (diferentemente do call e do apply, que executam a função logo em seguida quando o seu contexto é definido). Veja o exemplo abaixo:

import { MyButton } from 'my-buttons'
import React, { Component } from 'react'
class MyComponent extends Component {
constructor (props) {
super(props)
}
state = {
myState: 'Not clicked yet!'
}
componentDidMount () {
...
}
handleClick (e) {
e.preventDefault()
this.setState({
myState: 'Clicked Button!'
})

}
render () {
return (
<MyButton onClick={ this.handleClick }>
{this.state.myState}
</MyButton>
)
}
}

A ação de alterar o estado do componente acima através do botão não funcionará, porque a função handleClick é invocada pelo evento onClick do MyButton, tendo o seu contexto this alterado para o MyButton, e não para a classe MyComponent que possui o estado myState que precisa ser alterado.

Uma das soluções seria anexar o this do MyComponent através do bind no método handleClick no construtor do próprio MyComponent, desta forma:

...
class MyComponent extends Component {
constructor (props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
...

Mas a melhor forma é aproveitar uma feature do ES6 chamada Arrow Function, que é um tipo de função que preserva o escopo original, ou seja, sem alterá-lo. Assim, o exemplo ficará desta forma:

import { MyButton } from 'my-buttons'
import React, { Component } from 'react'
class MyComponent extends Component {
constructor (props) {
super(props)
}
state = {
myState: 'Not clicked yet!'
}
componentDidMount () {
...
}
handleClick = e => {
e.preventDefault()
this.setState({
myState: 'Clicked Button!'
})
}
render () {
return (
<MyButton onClick={ this.handleClick }>
{this.state.myState}
</MyButton>
)
}
}

Bem mais simples não?

Para alguns casos será necessário usar o método bind sim, quando por exemplo o método é repassado como prop para outro componente, que por sua vez repassa para outra função importada de outro módulo como injeção de dependência e que faz o setState (aposto que você se enrolou para entender isto rs), mas são raros os casos assim.

6. Divisão entre Stateful e Stateless Component

Stateful ou container ou smart… stateless ou presentational ou dumb… São vários os termos usados pela comunidade, mas afinal, qual a diferença entre eles, e quando usar cada um?

O React possui uma arquitetura unidirecional de dados, de transmissão de estados do componente mais alto para o componente mais baixo, que é responsável por dinamizar os componentes ou alterar o seu visual. Estes dados, como mencionei anteriormente, são passados de componente pai para componente filho via props. Porém quando muitos componentes na hierarquia possuem seu próprio estado e alteram por si só, o código fica bagunçado e fica difícil de gerenciar, por isso foi criado o conceito de Stateful e Stateless Components:

  • Stateful Component: é o único componente responsável por gerenciar todo o estado da aplicação, estando no topo da hierarquia. Normalmente é escrito como uma classe de JavaScript. Um dos exemplos acima é um stateful component:
import { MyButton } from 'my-buttons'
import React, { Component } from 'react'
class MyComponent extends Component {
constructor (props) {
super(props)
}
state = {
myState: 'Not clicked yet!'
}
componentDidMount () {
...
}
handleClick = e => {
e.preventDefault()
this.setState({
myState: 'Clicked Button!'
})
}
render () {
return (
<MyButton onClick={ this.handleClick }>
{this.state.myState}
</MyButton>
)
}
}
  • Stateless Components: são os componentes abaixo que apenas recebem (ou não recebem nada) o estado do componente stateful via prop e exibem algo na tela baseando-se nestas informações. Normalmente é uma função pura. No exemplo do stateful acima, o MyButton seria um componente stateless, que ao ser clicado, exibirá o texto Clicked Button!:
const MyButton = ({ children, onClick }) => (
<button onClick={onClick}>
{children}
</button>
)

7. Separação de códigos e arquivos

Um dos maiores receios ou contras dos devs que não são a favor do React é o fato dele misturar código HTML com JavaScript. Esta afirmação não está errada, mas isto não significa que isto seja um problema, desde que as responsabilidades sejam bem separadas. Normalmente eu separo em 3 arquivos:

  • index.jsx: arquivo de entrada do componente, também responsável por fazer decorators (feature do EcmaScript que está no stage 2) de Higher-Order Components (explicação mais detalhada em um tópico adiante). Como por exemplo, temos o connect do Redux;
  • Component.jsx: aqui vai toda a lógica do componente, com lifecycles, setStates etc. Aqui o componente é impuro, stafeful;
  • view.jsx: aqui não vai lógica nenhuma, apenas a view, o template, ou o código JSX. No máximo aceita import de funções que são helpers para tratar os dados. Aqui o componente é puro, stateless.

Podem haver mais arquivos no componente, como arquivos de estilo, de testes, de Storybook etc. Falarei um pouco mais deste caso adiante.

O método setState

Além da questão do bind, o método setState tem algumas particularidades pouco conhecidas, como por exemplo, além de aceitar um objeto, ele também aceita uma função como parâmetro:

this.setState(() => ({ counter: 1 }))

Qual a vantagem de se usar como uma função?

O método setState é uma função assíncrona, ou seja, se você executá-la em determinados momentos, ela não funcionará:

class MyCounter extends Component {
state = {
counter: 0
}
componentDidMount () {
this.setState({ counter: this.state.counter + 1 })
this.setState({ counter: this.state.counter + 1 })
this.setState({ counter: this.state.counter + 1 })
this.setState({ counter: this.state.counter + 1 })
this.setState({ counter: this.state.counter + 1 })
}
render () {
return (
<p>{this.state.counter}</p>
)
}
}

O exemplo acima exibirá 1 na tela porque quando a partir do segundo setState são executados, o primeiro setState ainda não terminou de executar por causa de sua natureza assíncrona. Seria necessário que houvesse um setTimeout em cada um dos setStates seguintes, ou:

class MyCounter extends Component {
state = {
counter: 0
}
componentDidMount () {
this.setState(prevState => ({ counter: prevState.counter + 1 }))
this.setState(prevState => ({ counter: prevState.counter + 1 }))
this.setState(prevState => ({ counter: prevState.counter + 1 }))
this.setState(prevState => ({ counter: prevState.counter + 1 }))
this.setState(prevState => ({ counter: prevState.counter + 1 }))
}
render () {
return (
<p>{this.state.counter}</p>
)
}
}

Agora sim será exibido corretamente o valor 5 na tela!

Pode acontecer de você precisar executar um callback após a mudança de estado realizada pelo setState. Para isto, o método setState aceita um segundo parâmetro que é uma função de callback:

this.setState(
{ counter: this.state.counter + 1 },
funcExecAfterChangedState
)
this.setState({ counter: this.state.counter + 1 }, () => {
...
})

Entender Higher-Order Components

Quando se está realizando a composição de componentes, com certeza você se deparará com casos em que vários componentes possuem o mesmo comportamento. Como por exemplo, vários componentes que precisam fazer requisição no componentDidMount para uma mesma API para aproveitar uma informação em suas telas, convertendo o payload em um formato necessário e inserindo em um estado com setState. Outro exemplo são vários componentes que recebem as mesmas props, portanto possuem os mesmos defaultProps e propTypes. Tudo o que é lógica específica do React pode ser abstraído para um Higher-Order Component para ser reusado em outros componentes.

Outra utilidade dos HOCs é isolamento de responsabilidades. Quando em muitos momentos, além de evitar misturar código em diversos locais do componente, sujando o seu código, acabamos deixando os componentes puros para fazerem somente aquilo para que foram criados. Segue abaixo um excelente artigo com uma introdução bastante didática sobre HOCs:

Escrever Testes

Aprender React já é um desafio, escrever testes é um desafio que vai além. Falando primeiramente sobre estrutura de arquivos, como eu costumo usar Storybook também, eu geralmente crio 3 arquivos:

  • test.jsx: arquivo que contém os testes unitários e de integração dos componentes (alguns devs possuem a prática de separar os testes de integração ainda em outro arquivo);
  • story.jsx: contém o código do componente que será exibido no Storybook;
  • tests.jsx: uma abstração dos componentes e seus estados que serão reaproveitados tanto no test.jsx quanto no story.jsx.

Quanto aos testes em si, componentes sem estado e funcionais apenas snapshots bastam, porém componentes mais dinâmicos e complexos, recomendo libs como o Enzyme. Em um artigo anterior meu eu comentei mais detalhadamente a respeito, por favor confira aqui :

Há outras libs com implementação inclusive mais fácil do que do Enzyme que você pode acabar encontrando pela comunidade.

Quanto a exemplos de implementação, como é um assunto vasto e mais relacionado ao Ecossistema do React do que ao React em si, gostaria de deixar reservado para um futuro artigo.

Conclusão

Eu desejo e espero que tenha conseguido esclarecer algumas dúvidas e ajudado-o a superar alguns obstáculos. Segue abaixo um dos artigos que me inspiraram, mas que trata não só sobre obstáculos específicos do React, mas também outras dificuldades como em JavaScript, Webpack etc. comuns para quem está se interando com React:

Caso você tenha algo a considerar, ou outras sugestões de dificuldades que também considere importante, não deixe de comentar!

Muito obrigado pessoal e valeu! Até breve! o/

--

--