Artigo original: Learn React Context in 5 Minutes - A Beginner's Tutorial

A API React Context tornou-se a ferramenta de gerenciamento de estado (em inglês, state) favorita de muitos, por vezes substituindo completamente o Redux. Neste rápido tutorial de cinco minutos, você terá uma introdução sobre o que é o Context e como usá-lo!

Nota da tradução: nas traduções de textos a respeito de React e de outras tecnologias, sempre que os textos são relativos a versões anteriores da tecnologia, avisamos que, no momento da tradução, os resultados podem variar. Como o React passou por muitas mudanças em suas versões mais recentes desde que o texto original foi lançado, pode haver partes do código que não ajam como o esperado na época do lançamento do artigo original.  

Considere esta árvore, na qual as caixas inferiores representam diferentes componentes:

gevur92qwoxvdjnm12dw

Podemos adicionar states (ou, em português, estados) aos componentes inferiores com facilidade, mas, até agora, a única maneira de passar dados para um componente irmão era mover o state para um componente superior e depois mandá-lo de novo para baixo até o irmão por meio de props.

u20r26dtxyr6ek6krzsb

Se, mais tarde, notarmos que o irmão do componente com o state também precisa dos dados, temos que elevar o state novamente e passá-lo de volta para baixo:

wtlykrxnx8xi12h4wek4

Embora essa solução funcione, os problemas começam quando um componente em um ramo diferente precisar dos dados:

g3xrvthcw24izllvb58w

Nesse caso, precisamos passar o state do topo da aplicação, passando por todos os componentes intermediários, até aquele que precisa dos dados lá no fundo, mesmo que os níveis intermediários não precisem deles. Este processo tedioso e demorado é conhecido como prop drilling.

ey25z0hvmy31xiiqqwgq

É aí que entra a API Context. Ela oferece uma maneira de passar dados pela árvore de componentes por meio de um par provedor-consumidor (em inglês, provider-consumer) sem ter que passar props por cada nível. Pense nisso como os componentes brincando de bobinho com os dados – os componentes intermediários podem nem "saber" que alguma coisa está acontecendo:

ckfpokb2cz3ffmn8238i

Para demonstrar isso, vamos criar esta divertida (e utilíssima) imagem de troca de dia e noite.

3evdww

Se quiser ver o código completo, dê uma olhada no playground do Scrimba deste artigo.

Criando o contexto

Para começar, criamos um contexto. Como queremos que toda a aplicação tenha acesso a ele, vamos ao index.js e envolvemos a aplicação com ThemeContext.Provider.

Também passamos a prop value para o nosso provedor. Ele armazena os dados que queremos salvar. Por enquanto, vamos apenas passar a string 'Dia'.

import React from "react";
import ReactDOM from "react-dom";
import ThemeContext from "./themeContext";

import App from "./App";

ReactDOM.render(
  <ThemeContext.Provider value={"Dia"}>
    <App />
  </ThemeContext.Provider>,
  document.getElementById("root")
);

Consumindo o contexto com contextType

No momento, no App.js, estamos apenas retornando o componente <Imagem />.

import React from "react";
import Imagem from "./Imagem";

class App extends React.Component {
  render() {
    return (
      <div className="app">
        <Imagem />
      </div>
    );
  }
}

export default App;

Nosso objetivo é usar o Context para alternar os nomes de classe em Imagem.js de Dia para Noite, dependendo de qual imagem queremos renderizar. Para isso, adicionamos uma propriedade estática ao nosso componente chamada ContextType e, em seguida, usamos a interpolação de string para adicioná-la aos classNames no componente <Imagem />.

Agora, os classNames contêm a string da prop value. Observação: eu movi o ThemeContext para um arquivo próprio para evitar um bug.

import React from "react";
import Botao from "./Botao";
import ThemeContext from "./themeContext";

class Imagem extends React.Component {
  render() {
    const theme = this.context;
    return (
      <div className={`${theme}-image image`}>
        <div className={`${theme}-ball ball`} />
        <Botao />
      </div>
    );
  }
}

Imagem.contextType = ThemeContext;

export default Imagem;

Context.Consumer

Infelizmente, essa abordagem só funciona com componentes baseados em classes. Se você já aprendeu sobre hooks em React (texto em inglês), deve saber que podemos fazer praticamente qualquer coisa com componentes funcionais hoje em dia. Portanto, como boa prática, devemos converter nossos componentes em componentes funcionais e daí usar o componente ThemeContext.Consumer para passar informações pela aplicação.

Fazemos isso envolvendo os elementos em uma instância de <ThemeContext.Consumer> e, dentro dela, (onde os componentes filhos ficam), declarando uma função que retorna esses elementos. Isso usa o padrão "render prop", onde fornecemos como filho uma função normal que retorna o JSX que será renderizado.

import React from "react";
import Botao from "./Botao";
import ThemeContext from "./themeContext";

function Imagem(props) {
  // Não precisamos mais disso
  // const theme = this.context

  return (
    <ThemeContext.Consumer>
      {theme => (
        <div className={`${theme}-image image`}>
          <div className={`${theme}-ball ball`} />
          <Botao />
        </div>
      )}
    </ThemeContext.Consumer>
  );
}

// Não precisamos mais disso
// Imagem.contextType = ThemeContext;

export default Imagem;

Observação: também precisamos envolver o componente <Botao /> no <ThemeContext.Consumer> para adicionar funcionalidade ao botão mais tarde.

import React from "react";
import ThemeContext from "./themeContext";

function Botao(props) {
  return (
    <ThemeContext.Consumer>
      {context => (
        <button className="botao">
          Switch
          <span role="img" aria-label="sol">
            ?
          </span>
          <span role="img" aria-label="lua">
            ?
          </span>
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default Botao;

Extraindo o provedor do contexto

No momento, estamos passando um valor imutável para o provedor. No entanto, nosso objetivo é usar o botão para alternar entre noite e dia.

Isso requer mover nosso provedor para um arquivo separado e colocá-lo em seu próprio componente, que, neste caso, é chamado de ThemeContextProvider.

import React, { Component } from "react";
const { Provider, Consumer } = React.createContext();

class ThemeContextProvider extends Component {
  render() {
    return <Provider value={"Dia"}>{this.props.children}</Provider>;
  }
}

export { ThemeContextProvider, Consumer as ThemeContextConsumer };

Observação: a propriedade value, agora, está sendo gerenciada no novo arquivo ThemeContext.js e, portanto, deve ser removida de index.js.

Alterando o contexto
Para configurar o botão, primeiro adicionamos o state ao ThemeContextProvider:

import React, { Component } from "react";
const { Provider, Consumer } = React.createContext();

// Note: você também pode usar hooks para criar o state e converter num componente funcional.
class ThemeContextProvider extends Component {
  state = {
    theme: "Dia"
  };
  render() {
    return <Provider value={"Dia"}>{this.props.children}</Provider>;
  }
}

export { ThemeContextProvider, Consumer as ThemeContextConsumer };

Em seguida, adicionamos um método para alternar entre dia e noite:

toggleTheme = () => {
  this.setState(prevState => {
    return {
      theme: prevState.theme === "Dia" ? "Noite" : "Dia"
    };
  });
};

Agora, alteramos nossa propriedade value para this.state.theme para que ela retorne as informações vindas do state.

 render() {
    return <Provider value={this.state.theme}>{this.props.children}</Provider>;
  }
}

Em seguida, alteramos value para um objeto contendo {theme: this.state.theme, toggleTheme: this.toggleTheme} e atualizamos todos os locais onde usamos um único valor para procurar o theme em um objeto. Isso significa que cada theme se torna context e cada referência ao theme como valor se torna context.theme.

Por fim, dizemos ao botão para escutar o evento onClick e, em seguida, acionar o context.toggleTheme – isso atualiza os consumidores que estão usando o state do provedor. O código para o botão fica assim:

import React from "react";
import { ThemeContextConsumer } from "./themeContext";

function Botao(props) {
  return (
    <ThemeContextConsumer>
      {context => (
        <button onClick={context.toggleTheme} className="botao">
          Switch
          <span role="img" aria-label="sol">
            ?
          </span>
          <span role="img" aria-label="lua">
            ?
          </span>
        </button>
      )}
    </ThemeContextConsumer>
  );
}

export default Botao;

Nosso botão, agora, alterna a imagem entre a noite e o dia com um só clique!

3evdww-1

Ressalvas sobre o Context

Como todas as coisas boas na programação, existem algumas ressalvas no uso do Context:

Não use o Context para evitar prop drilling em apenas uma ou duas camadas. O Context é ótimo para gerenciar os state necessários para grandes porções de uma aplicação. No entanto, prop drilling é mais rápido se você estiver passando informações por algumas poucas camadas.

Evite usar o Context para salvar um state que deveria ser mantido localmente. Ou seja, se você precisar salvar as entradas de formulário de um usuário, por exemplo, use o state local em vez do Context.

Sempre envolva o provedor em torno do pai comum mais baixo possível na árvore, não do componente de nível mais alto na aplicação. Não precisa exagerar.

Por fim, se você passar um objeto como prop, monitore o desempenho e refatore conforme necessário. Isso, provavelmente, não será necessário, a menos que uma queda no desempenho seja perceptível.

Resumo

Este exemplo é bastante simples e, provavelmente, seria mais fácil colocar o state no App e passá-lo por meio de props. No entanto, espero que isso mostre o poder de usar os consumidores, que podem acessar dados independentemente dos componentes acima deles na árvore.

Se você quiser uma introdução mais detalhada sobre o assunto, pode entrar na lista de espera para o meu próximo curso avançado de React (em inglês), ou, se ainda for um iniciante, confira meu curso introdutório gratuito de React.

Boa programação para você! 😀