Criando contextos com o hook useContext

Criando contextos com o hook useContext

Antigamente, lá na época de 1970, quando ainda não haviam sistemas modernos de comunicações móveis, tais como as tecnologias 1G, 2G, 3G, 4G... 5G, a comunicação se dava de forma analógica, onde qualidade da voz e a cobertura de rede eram super limitadas.

No quesito de telefonia analógica, as ligações eram transmitidas como sinais elétricos analógicos, onde os aparelhos deveriam estar conectados a um sistema de cabeamento que os conectava a uma infraestrutura física de cabos, geralmente feitos de cobre, que ligava os telefones entre si e à central telefônica mais próxima.

Então se você quisesse ligar para algum familiar ou conhecido, tanto você quanto a pessoa deveriam ter aparelhos telefônicos que estivessem interconectados com à central telefônica.

Se por um acaso houvesse algum problema no cabeamento, ou quem sabe um aparelho estivesse desconectado, a ligação não seria concretizada.

Na ilustração acima, temos o telefone A e o telefone B conectados a central telefônica, o que possibilita uma conexão entre ambos.

Já nos anos de 1980 em diante, uma nova tecnlogia surgiu e ficou conhecida como tecnologia móvel, o que possibilitou que os novos aparelhos telefônicos pudessem se comunicar via ondas de rádio por meio de torres terrestres, ou via satélites 🛰️

Dessa vez, os aparelhos poderiam estar em qualquer lugar do planeta e trocar informações entre si, sem a necessidade de estarem conectados por meio de um cabeamento físico.

Legal, mas o que essa história toda tem haver com a jornada do desenvolvedor ReactJS? 🧐

É o que iremos descobrir agora 😄

Passando informações para componentes

Em lições passadas, você aprendeu a fazer o uso das props em componentes funcionais.

Onde você viu que um componente pai pode passar informações e dados para um componente filho e assim por diante. Por exemplo:

Componente Pai:

// Componente Pai
import React from 'react';
import Filho from './Filho';

const Pai = () => {
 const mensagemParaFilho = "Olá, filho! Esta é uma mensagem do Pai.";

 return (
 <div>
 <h2>Componente Pai</h2>
 <Filho mensagem={mensagemParaFilho} />
 </div>
 );
};

export default Pai;

Componente Filho:

// Componente Filho
import React from 'react';

const Filho = (props) => {
 return (
 <div>
 <h3>Componente Filho</h3>
 <p>Mensagem do Pai: {props.mensagem}</p>
 </div>
 );
};

export default Filho;

Caso o componente filho quisesse passar uma informação para outro componente filho, ele poderia fazer isso seguindo a mesma estrutura de props, onde um componente pai passaria uma informação para o seu filho, que por sua vez passaria para outro filho e assim por diante.

Fazendo uma representação do que acabamos de ver nos códigos acima, teriamos uma estrutura representada da seguinte maneira:

A representação é bem similar ao modelo cascata, onde um componente recebe todas as informações e vai repassar para os componentes seguintes.

O que também vai de encontro ao modelo analógico de telecomunicações que revisamos logo no início desta lição 🙂

Só que, apesar dessa prática ser comum em projetos feitos com ReactJS, isso pode acabar se tornando um inconveniente, de modo que a sua aplicação se torne propensa a ter problemas a medida em que ela cresce e se torna mais complexa, devido aos seguintes fatos:

Prop drilling (ou "perfuração de props"): À medida que a aplicação cresce e a hierarquia de componentes se torna mais profunda, pode ser necessário passar as mesmas props por vários níveis de componentes intermediários. Isso pode levar ao que é chamado de "prop drilling", onde as props são passadas através de vários componentes que não precisam delas, apenas para chegar ao componente que realmente as utiliza.

Dificuldade de manutenção: Proporcionar um grande número de props pode tornar o código mais difícil de entender, manter e depurar, especialmente se muitos componentes estiverem envolvidos.

Acoplamento excessivo: O componente filho torna-se fortemente acoplado ao componente pai, uma vez que ele depende das props que recebe do pai. Isso pode tornar os componentes menos reutilizáveis e mais difíceis de testar de forma isolada.

Desempenho: Em aplicações maiores, a passagem de muitas props pode ter um impacto negativo no desempenho, especialmente se as props forem atualizadas frequentemente.

Além disso, durante a sua jornada, você pode se deparar com a seguinte questão: "De que forma um componente filho pode se comunicar de volta com o pai?".

Passando funções do componente pai para o componente filho

Existem varias maneiras em que um componente filho pode se comunicar de volta e passar informações ao componente pai.

Uma dessas alternativas envolve a passagem de funções que estão declaradas dentro de um componente pai, mas que só serão executadas dentro do componente filho.

Componente Pai:

// Componente Pai
import React, { useState } from 'react';
import Filho from './Filho';

const Pai = () => {
 const [mensagemRecebida, setMensagemRecebida] = useState("");

 const receberMensagem = (mensagem) => {
 setMensagemRecebida(mensagem);
 };

 return (
 <div>
 <h2>Componente Pai</h2>
 <p>Mensagem Recebida: {mensagemRecebida}</p>
 <Filho enviarMensagem={receberMensagem} />
 </div>
 );
};

export default Pai;

Componente Filho:

// Componente Filho
import React from 'react';

const Filho = (props) => {
 const handleClick = () => {
 props.enviarMensagem("Olá, Pai! Esta é uma mensagem do Filho.");
 };

 return (
 <div>
 <h3>Componente Filho</h3>
 <button onClick={handleClick}>Enviar Mensagem para o Pai</button>
 </div>
 );
};

export default Filho;

Seguindo essa lógica, o componente filho poderia passar uma informação que seria recebida pelo componente pai, de modo a influenciar o JSX deste componente.

O problema maior nesses casos é o "prop drilling", pois imagine que a função do componente pai só seria usada no componente filho do filho do filho... loucura não acha? 😧

Para evitar isso, nós temos 2 alternativas:

  • Fazer o uso do hook useContext,
  • Ou fazer o uso da biblioteca Redux ou outras que controlam estados personalizados. (Veremos como isso funciona futuramente)

Nesta lição, vamos colocar em prática o uso do hook useContext 😉

Criando seu projeto de testes

Antes de começarmos, vamos criar um novo projeto em ReactJS dedicado a esta lição. No meu caso criei um novo projeto chamado de use-context:  

npx create-react-app use-context

Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁

Feito isso, vamos aprender um pouco mais sobre o uso de uma propriedade especial conhecida como children (componentes acoplados).

Componentes Acoplados

No mundo do ReactJS, nós já vimos como fazemos para criar componentes e também como passar informações para dentro deles usando as famosas props.

Mas, existe um tópico especial que ainda não vimos: Acoplação de Componentes ou Componentes de Container.

No HTML nós temos algumas tags que nos permitem aninhar elementos dentro de outros elementos, sabe que tags são essas?

Tags como <div>, <span>, <p>, <ul>, <li> e algumas outras, nos permitem que possamos inserir outros elementos HTML dentro delas. Essas tags são conhecidas como "tags contêineres" ou "tags de conteúdo", por exemplo:

<div>
 <p>Olá Micilini =)</p>
</div>

<ul>
 <li><span>Item 1</span></li>
 <li><span>Item 2</span></li>
 <li><span>Item 3</span></li>
</ul>

Note que no código HTML acima, temos uma <div> e um <ul> encapsulando (servindo como um container) todos os outros elementos HTML.

Algumas dessas tags são fundamentais para a estruturação e organização de conteúdo em documentos HTML, principalmente quando estamos trabalhando com layouts complexos em conjunto com a hierarquia desses elementos.

Mas será que conseguimos simular isso com um componente no ReactJS?

Sim, isso é totalmente possível, vejamos um exemplo clássico:

return(
 <>
 <MeuComponente>
 <p>Olá ReactJS =)</p>
 </MeuComponente>
 </>
)

Só que para isso, vamos precisar criar Componentes Acoplados 🤩

Trabalhando com componentes acoplados

Quando nos criamos um componente funcional ou de estado, nós podemos definir o recebimento de uma prop especial chamada de children

Que por sua vez, contém todos os elementos HTML nas quais nosso componente está encapsulando.

Para testarmos isso, vamos criar um novo componente chamado de MeuComponente.

MeuComponente > index.jsx:

const MeuComponente = ({ children }) => {
 return (
 <div>
 <h2>Meu Componente</h2>
 {children}
 </div>
 );
}

export default MeuComponente;

Não se esqueça de fazer as devidas referências no App.js:

import MeuComponente from "./components/MeuComponente";

export default function App() {
 return(
 <div>
 <MeuComponente />
 </div>
 )
}

Analisando os comandos do App.js, podemos notar que estamos inserindo o nosso componente dentro da tela normalmente, como vinhamos fazendo em lições passadas.

A diferença é que agora o nosso componente (MeuComponente) está recebendo uma nova propriedade chamada de children cuja tradução é crianças.

E isso significa dizer que a partir de agora, nós podemos transformar o nosso componente em um container de modo a passar outros elementos HTML e JSX acoplados, observe:

import MeuComponente from "./components/MeuComponente";

export default function App() {
 return(
 <div>
 <MeuComponente>
 <h3>Olá, eu sou um filho do componente MeuComponente</h3>
 </MeuComponente>
 </div>
 )
}

Veja como ficou o resultado final:

Todos os elementos JSX que estiverem sendo encapsulados pelo MeuComponente, serão armazenados dentro da variável children.

Observe um outro exemplo em que passamos mais de um elemento:

import MeuComponente from "./components/MeuComponente";

export default function App() {
 return(
 <div>
 <MeuComponente>
 <p>Este é um componente filho de MeuComponente.</p>
 <button>Clique aqui</button>
 </MeuComponente>
 </div>
 )
}

Será que podemos passar códigos Javascript para dentro do children?

Sim, observe um exemplo de renderização condicional:

import MeuComponente from "./components/MeuComponente";

export default function App() {
 const mostrarMensagem = true;

 return (
 <div>
 <MeuComponente>
 {mostrarMensagem && <p>Esta é uma mensagem condicional.</p>}
 </MeuComponente>
 </div>
 );
}

Observação: Tenha em mente que alguns comandos de Javascript puro podem ser interpretados como texto quando passados como children, por exemplo:

import MeuComponente from "./components/MeuComponente";

export default function App() {
 const codigoJavaScript = "<script>alert('Olá, mundo!');</script>";

 return (
 <div>
 <MeuComponente>
 {codigoJavaScript}
 </MeuComponente>
 </div>
 );
}

E como passamos props em conjunto com a variável children?

Para passar props junto com o children no ReactJS, você pode simplesmente passar as props normalmente dentro do componente pai.

App.js:

// Componente Pai
import React from 'react';
import MeuComponente from './MeuComponente';

const App = () => {
 const cor = "red";

 return (
 <div>
 <MeuComponente corDeFundo={cor}>
 <p>Este é um componente filho com cor de fundo personalizada.</p>
 </MeuComponente>
 </div>
 );
};

export default App;

MeuComponente > index.jsx:

// Componente Filho (MeuComponente)
import React from 'react';

const MeuComponente = ({ corDeFundo, children }) => {
 return (
 <div style={{ backgroundColor: corDeFundo }}>
 {children}
 </div>
 );
};

export default MeuComponente;

Note que no exemplo acima a variável que recebe o conteúdo HTML é sempre a última a ser declarada (que no caso é a children).

Posso mudar o nome da variável children?

Sim, pois o uso do children é apenas uma convenção da comunidade de desenvolvedores (e que é bom adotá-la), mas não é uma palavra-chave reservada.

// Componente Filho (MeuComponente)
import React from 'react';

const MeuComponente = ({ micilini }) => {
 return (
 <div>
 {micilini}
 </div>
 );
};

export default MeuComponente;

Podemos acoplar um outro componente para dentro de children?

Sim, isso é totalmente possível, veja como é fácil.

MeuComponente > index.jsx:

const MeuComponente = ({ children }) => {
 return (
 <div>
 <h2>Meu Componente</h2>
 <div>{children}</div>
 </div>
 );
};

export default MeuComponente;

ComponenteFilho > index.jsx:

const ComponenteFilho = () => {
 return <p>Este é um componente filho.</p>;
};

export default ComponenteFilho;

App.js:

const App = () => {
 return (
 <div>
 <MeuComponente>
 <ComponenteFilho />
 <p>Eu sou um elemento HTML avulso!</p>
 </MeuComponente>
 </div>
 );
};

O que podemos passar para dentro do children?

A variável children em um componente ReactJS pode conter qualquer tipo de conteúdo, não apenas elementos HTML, como por exemplo:

Elementos ReactJS: Além de elementos HTML, você pode passar outros componentes feitos em ReactJS como children.

Strings e números: Você também pode passar strings ou números como children. Onde no final, eles sempre mserão renderizados como texto dentro do componente.

Expressões JSX: Você pode passar expressões JSX como children, que podem incluir elementos HTML, componentes ReactJS e outros tipos de conteúdo.

Veja os exemplos abaixo:

import React from 'react';
import MeuComponente from './MeuComponente';

const App = () => {
 const texto = "Este é um texto simples.";
 const numero = 123;

 return (
 <div>
 <MeuComponente>
 {texto}
 <p>Este é um parágrafo dentro do children.</p>
 {numero}
 <button>Clique aqui</button>
 </MeuComponente>
 </div>
 );
};

export default App;

No exemplo acima, texto é uma string, numero é um número e <button>Clique aqui</button> é um elemento HTML junto com o <p>, onde todos eles coexistem dentro do children

Perfeito, agora que você já entende o uso dos componentes acoplados, podemos então aprender sobre o hook useContext (uma vez que ele também faz o uso do children 😆).

useContext

No mundo do ReactJS, nós temos acesso a mais um hook que também está presente dentro do framework, chamado de useContext (ou Context API).

No início desta lição você aprendeu um pouco sobre o funcionamento da técnologia móvel, onde diversos aparelhos celulares independentes se comunicavam uns com os outros por meio de ondas de rádio ou satelites.

Hoje, você verá que o useContext não é diferente tão diferente assim das "tecnologias móveis" 😉

O useContext é um hook do ReactJS que permite acessar o contexto de um componente funcional.

Ele é utilizado para consumir valores fornecidos por um componente Provedor (Provider) em uma árvore de componentes do ReactJS, tudo isso, sem precisar passar props manualmente por cada nível da hierarquia.

Ou seja, se formos representar o que acabamos de ler acima, teriamos uma estrutura parecida com esta:

Observe que o Context API é um componente que pode envolver toda a sua aplicação, ou apenas uma parte dela.

Fazendo com que todos os componentes acoplados tenham acesso às informações que precisam, ou até mesmo passar informações entre si.

Portanto, o contexto no ReactJS é uma ferramenta que permite compartilhar dados entre componentes sem precisar passar explicitamente através das props.

Isso é particularmente útil em situações onde diversos componentes precisam acessar os mesmos dados, como temas, preferências do usuário, autenticação, etc.

Observe a diferença entre estrutura de uso das props, e a estrutura de uso de um contexto:

Quando usar props e quando usar contextos?

"Isso significa que devemos abandonar as props e passar a usar os contextos?".

Não necessariamente. Ambas as props e contextos têm seus lugares e propósitos em uma aplicação ReactJS, e a escolha entre eles depende do contexto específico e das necessidades do seu aplicativo.

Resumidamente se o nível de hierarquia para se passar informações for muito grande, onde temos um componente pai que tem que passar uma informação para o componente filho do filho do filho... e em diante, o uso do contexto se torna uma alternativa ideal.

Mas se for algo de apenas um único nível, como por exemplo, um componente pai precisar passar alguma informação para o componente filho e além disso, não há problema em continuar usando props.

Vejamos abaixo alguns cenários comuns em que o uso dos contextos podem ser preferíveis:

Dados globais: Se você tem dados que serão consumidos por muitos componentes em diferentes partes da sua aplicação, um contexto pode ser uma opção mais conveniente do que passar as props manualmente por toda a árvore de componentes.

Preferências do usuário: Se você tem preferências do usuário, como tema, idioma, modo escuro, etc., que afetam muitos componentes, um contexto pode ser uma maneira eficiente de compartilhar essas preferências em toda a aplicação.

Autenticação do usuário: Dados de autenticação, como informações de login ou tokens de acesso, que precisam ser acessíveis em muitos componentes diferentes, podem ser gerenciados de forma eficaz usando um contexto.

Em resumo, props e contextos são ferramentas diferentes que podem ser usadas em diferentes situações.

Props são melhores para comunicação direta entre componentes, enquanto contextos são úteis para compartilhar dados globais em toda a aplicação.

Em muitos casos, você pode acabar usando ambos em conjunto para diferentes partes da sua aplicação 🙃

Trabalhando com o useContext

Creio que você já esta doido(a) para colocar a mão na massa, não é verdade?

Então chega de conversa vamos enfim aprender a fazer o uso do useContext.

Primeiro de tudo! Você precisa criar uma nova pasta dentro de src chamada de contexts, onde você deverá armazenar todos os contextos em que a sua aplicação poderá fazer o uso.

E sim, dentro da sua aplicação poderão existir diversos contextos diferentes 😉

Agora, dentro da pasta contexts vamos criar o nosso primeiro contexto chamado de TemaProvider, imaginando que queremos armazenar as configurações do tema do nosso site, de modo que tais dados possam ser acessados por todos os componentes da nossa aplicação.

Contexts > TemaProvider.js:

import React, { createContext } from 'react';

// Criando o contexto
const TemaContext = createContext();

// Provedor do contexto
const TemaProvider = ({ children }) => {
 const tema = 'claro'; // Exemplo de tema

 return (
 <TemaContext.Provider value={tema}>
 {children}
 </TemaContext.Provider>
 );
};

export { TemaProvider, TemaContext };

Ok, agora vamos detalhar cada parte desse código!

Para se trabalhar com contextos no ReactJS (e considerando que ele também é um hook) a primeira coisa que você precisa fazer é importá-lo usando o import da seguinte forma:

import React, { createContext } from 'react';

Lembrando que você também pode usar diversos hooks em conjunto com o useContext, observe:

import React, { createContext, useState } from 'react';

Em seguida, dentro de um mesmo arquivo foi criado duas constantes, uma que armazena o contexto e a outra que armazena um Provider.

TemaContext: O TemaContext é o contexto em si. Ele é criado usando a função createContext() do ReactJS. O contexto é essencialmente um objeto que fornece uma maneira de passar dados através da árvore de componentes sem a necessidade de passar props manualmente em cada nível.

TemaProvider: O TemaProvider é um componente ReactJS que envolve a árvore de componentes e fornece o contexto do tema para todos os componentes filhos. Ele é responsável por fornecer o valor do contexto para todos os componentes que consomem aquele contexto.

Como você pode perceber, o TemaProvider nada mais é do que um componente que vai receber conteúdos HTML e JSX (usando o children).

Em seguida dentro do return, nós temos os seguintes comandos:

<TemaContext.Provider>: Aqui, estamos usando o componente Provider do contexto TemaContext.

No ReactJS, quando você cria um contexto usando React.createContext() ou createContext(), ele retorna um objeto que possui uma propriedade chamada Provider.

O Provider é usado para envolver a árvore de componentes onde você deseja disponibilizar o contexto e fornecer os dados para os componentes filhos.

value={tema}: A propriedade value do componente Provider é onde você passa os dados que deseja compartilhar com os componentes filhos. Neste caso, tema é uma variável que contém o valor do tema que você deseja disponibilizar para os componentes filhos.

De modo a fazer com que todos os componentes que consomem o contexto TemaContext poderão acessar esse valor através do comando useContext(TemaContext).

{children}: Dentro do Provider, estamos renderizando o {children}.

Isso permite que todos os componentes filhos que estão envolvidos pelo Provider tenham acesso ao contexto fornecido por ele.

O children é uma prop especial que contém todos os elementos filhos passados para um componente ReactJS.

Portanto, todos os componentes filhos de <TemaContext.Provider> terão acesso ao contexto do tema fornecido pelo Provider.

Por fim, estamos exportando tanto o TemaProvider quanto o TemaContext, onde o TemaProvider será usado dentro de um componente pai para envolver a árvore de componentes que precisam acessar o contexto do tema.

E o TemaContext, que só será utilizado pelos componentes filhos para consumir o contexto do tema (informações e dados compartilhados) usando o hook useContext(TemaContext).

Legal, e como usamos esse contexto dentro da nossa aplicação?

Após criar o arquivo TemaProvider.js dentro da pasta contexts. Vamos importá-lo dentro do App.js da seguinte forma:

import { TemaProvider } from "./contexts/TemaProvider";
import SegundoComponente from "./components/SegundoComponente";

export default function App() {
 return (
 <div>
 <TemaProvider>
 <SegundoComponente />
 </TemaProvider>
 </div>
 );
}

Observe que estamos importando o TemaProvider usando as chaves e encapsulando o SegundoComponente.

Já dentro do SegundoComponente, podemos acessar a variável tema da seguinte forma:

import React, { useContext } from "react";
import { TemaContext } from "../../contexts/TemaProvider";

const SegundoComponente = () => {
 const tema = useContext(TemaContext);
 return (
 <div>
 <h2>Meu Segundo Componente</h2>
 <p>O tema da aplicação é: {tema}</p>
 </div>
 );
}

export default SegundoComponente;

Note que estamos importando o TemaContext para dentro do nosso componente e usando o comando useContext(TemaContext) de modo a retornar a variável tema.

Veja como ficou o resultado final:

"Como o comando useContext(TemaContext) retorna a variável tema se eu nem especifiquei isso?"

O comando useContext(TemaContext) retorna o valor definido na propriedade value do componente <TemaContext.Provider>.

No código do TemaProvider.js, nós passamos o valor tema como value para o <TemaContext.Provider> da seguinte forma:

<TemaContext.Provider value={tema}>
 {children}
</TemaContext.Provider>

Quando você chama useContext(TemaContext) em um componente filho, o ReactJS vai procurar pelo contexto mais próximo na árvore de componentes, neste caso, o <TemaContext.Provider>.

Ele então retornará o valor especificado na propriedade value desse Provider, que no seu caso é a variável tema.

"Legal, mas e se houvessem mais de uma variável sendo passada no Provider?".

Passando mais de uma variável para dentro do Provider de um determinado contexto

Supondo que dentro do TemaProvider.js, você queria passar mais duas variáveis para um determinado contexto além do tema, você pode fazer isso usando objetos da seguinte forma:

Contexts > TemaProvider.js:

import React, { createContext } from 'react';

// Criando o contexto
const TemaContext = createContext();

// Provedor do contexto
const TemaProvider = ({ children }) => {
 const tema = 'claro'; // Exemplo de tema
 const site = 'https://www.micilini.com'; // Exemplo de site
 const rank = 1; // Exemplo de rank

 return (
 <TemaContext.Provider value={{ tema: tema, site: site, rank: rank }}>
 {children}
 </TemaContext.Provider>
 );
};

export { TemaProvider, TemaContext };

Ou quem sabe, você pode passar essas variaveis diretamente:

import React, { createContext } from 'react';

// Criando o contexto
const TemaContext = createContext();

// Provedor do contexto
const TemaProvider = ({ children }) => {
 const tema = 'claro'; // Exemplo de tema
 const site = 'https://www.micilini.com'; // Exemplo de site
 const rank = 1; // Exemplo de rank

 return (
 <TemaContext.Provider value={{ tema, site, rank }}>
 {children}
 </TemaContext.Provider>
 );
};

export { TemaProvider, TemaContext };

Já dentro do seu componente, basta acessar os objetos por meio da técnica da desestruturação, observe:

SegundoComponente > index.jsx:

import React, { useContext } from "react";
import { TemaContext } from "../../contexts/TemaProvider";

const SegundoComponente = () => {
 const {tema, site, rank} = useContext(TemaContext);
 return (
 <div>
 <h2>Meu Segundo Componente</h2>
 <p>O tema da aplicação é: {tema}</p>
 <p>O site é: {site}</p>
 <p>O rank é: {rank}</p>
 </div>
 );
}

export default SegundoComponente;

Veja como ficou o resultado final:

Declarando funções dentro de contextos

Supondo agora que dentro do nosso TemaProvider.js eu tenha criado uma função responsável por exibir uma mensagem de alerta no navegador do usuário, nós podemos fazer isso da seguinte forma:

import React, { createContext } from 'react';

// Criando o contexto
const TemaContext = createContext();

// Provedor do contexto
const TemaProvider = ({ children }) => {
 const tema = 'claro'; // Exemplo de tema
 const site = 'https://www.micilini.com'; // Exemplo de site
 const rank = 1; // Exemplo de rank

 // Função para exibir uma mensagem de alerta
 const exibirAlerta = () => {
 alert('Esta é uma mensagem de alerta!');
 };

 return (
 <TemaContext.Provider value={{ tema: tema, site: site, rank: rank, exibirAlerta }}>
 {children}
 </TemaContext.Provider>
 );
};

export { TemaProvider, TemaContext };

Já dentro do SegundoComponente basta executar a seguinte lógica:

import React, { useContext } from "react";
import { TemaContext } from "../../contexts/TemaProvider";

const SegundoComponente = () => {
 const {tema, site, rank, exibirAlerta} = useContext(TemaContext);

 const handleClick = () => {
 exibirAlerta();
 };
 
 return (
 <div>
 <h2>Meu Segundo Componente</h2>
 <p>O tema da aplicação é: {tema}</p>
 <p>O site é: {site}</p>
 <p>O rank é: {rank}</p>
 <button onClick={handleClick}>Exibir Alerta</button>
 </div>
 );
}

export default SegundoComponente;

Note que trazemos do nosso contexto a variável exibirAlerta(), onde a estamos executando assim que o usuário clica no botão.

Usando estados em conjunto com contextos

Durante a sua jornada como desenvolvedor, você não só vai se deparar como também vai precisar criar contextos que fazem o uso do useState.

Pois do que adianta criar contextos nas quais não podemos alterar suas informações, não é verdade?

Pensando nisso, vamos criar um novo contexto chamado de UsersProvider dentro da pasta contexts, que será responsável por armazenar os dados dos nossos usuários.

Contexts > UsersProvider.js:

import React, { useState, createContext } from 'react';

// Criando o contexto
const UsersContext = createContext();

// Provedor do contexto
const UsersProvider = ({ children }) => {
 const [nome, setNome] = useState('Usuário');
 const [email, setEmail] = useState('teste@email.com');

 return (
 <UsersContext.Provider value={{ nome, setNome, email, setEmail }}>
 {children}
 </UsersContext.Provider>
 );
 };
 
 export { UsersProvider, UsersContext };

Note que estamos passando dentro de value os estados sem a necessidade de criar chaves de objetos. Essa é uma segunda maneira de se passar informações.

Em seguida, vamos criar um novo componente chamado de Dashboard.

Dashboard > index.jsx:

import React, { useContext } from "react";
import { UsersContext } from "../../contexts/UsersProvider";

const Dashboard = () => {
 const { nome, setNome, email, setEmail } = useContext(UsersContext);

 const changeToMicilini = () => {
 setNome('Micilini');
 setEmail('hey@micilini.com');
 }

 const changeToSomeone = () => {
 setNome('Usuário');
 setEmail('teste@email.com');
 }

 return(
 <>
 <h2>Dashboard da Aplicação</h2>
 <p>Nome do Usuário: {nome}</p>
 <p>Email do Usuário: {email}</p>
 <button onClick={changeToMicilini}>Fazer Login como Micilini</button>
 <button onClick={changeToSomeone}>Fazer Login como Usuário Avulso</button>
 </>
 )
}

export default Dashboard;

Por fim, não se esqueça de importar tanto o UsersProvider quanto o Dashboard para dentro do App.js:

import { UsersProvider } from "./contexts/UsersProvider";
import Dashboard from "./components/Dashboard";

export default function App() {
 return (
 <div>
 <UsersProvider>
 <Dashboard />
 </UsersProvider>
 </div>
 );
}

Veja como ficou o resultado final:

No caso do UsersContext todos os componentes pertencentes a ele podem ter acesso aos estados nome, setNome, email, setEmail e etc...

Usando contextos em conjunto com useEffect

Uma das possibilidades de um contexto no ReactJS é a usabilidade do hook useEffect dentro do Context API.

Quando você usa o useEffect dentro de um componente funcional em ReactJS, ele é executado após a renderização do componente e também em respostas a mudanças em variáveis específicas.

Dentro de um contexto em ReactJS, o useEffect pode ser usado para realizar efeitos colaterais, como buscar dados de uma API, atualizar o estado, ou qualquer outra operação que precise ser realizada quando o contexto é modificado.

Abaixo está um exemplo de um contexto chamado de HelloProvider que executa uma mensagem de olá, mundo:

import React, { createContext, useContext, useEffect, useState } from 'react';

// Criando o contexto
const HelloContext = createContext();

// Provedor do contexto
const HelloProvider = ({ children }) => {
 const [message, setMessage] = useState('');

 useEffect(() => {
 setMessage('Olá, mundo!');
 }, []);

 return (
 <HelloContext.Provider value={message}>
 {children}
 </HelloContext.Provider>
 );
};

Em aplicações reais, o useEffect pode ser usado dentro de um contexto para buscar informações do usuário, e realizar validações para saber se nosso usuário se encontra logado ou não.

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Recapitulando, voce não precisa usar o Context API sempre na sua aplicação.

Ele somente se faz necessário quando você precisa espalhar alguma informação ou algum dado de forma global dentro do seu sistema.

Um grande exemplo do uso de Context API é quando você tem um sistema de login na qual você precisa dar acesso global a informações como nome do usuário, email, nível de acesso e afins.

Um outro exemplo do uso do Context API são aqueles ícones que mostram a quantidade de notificações, mensagens ou itens carrinho de compras. Onde nestes casos cada elemento daquele pode estar separado em um módulo (componente diferente), e a única forma de mantê-los conectados é por meio do Context API ou usando props (cuidado com o prop drilling).

Espero que essa lição tenha sido de bom proveito na sua jornada como desenvolvedor.

E lembre-se, você pode voltar sempre que quiser para relembrar alguns conceitos.

Até a próxima lição 🙂