Context, é uma forma de passar dados pela árvore de componentes react, sem que seja necessário passar props manualmente para todos os componentes.

Normalmente, você passa informações usando props para os seus componentes.

Mas isso pode se tornar confuso e difícil de manter se você tiver que passá-las muitos níveis abaixo do componente pai.

Dessa forma, a context API permite que o componente pai forneça qualquer tipo de informação para qualquer componente que esteja abaixo dele.

Introdução ao projeto de exemplo

Antes de tudo, se você quiser, pode ver o código-fonte desse artigo clicando aqui.

O projeto é um contador (count down), que, a princípio, mantém toda a lógica num só componente.

Isso faz com que o componente cresça muito e dificulte o entendimento do mesmo. Sem contar que se navegarmos para outra página o count down para de ser atualizado.

Código do count down

// CountDown.tsx
import { useEffect, useState } from "react";
import styles from "./styles.module.css";

export function CountDown() {
  const [startTimer, setStartTimer] = useState(0);
  const [minutes, setMinutes] = useState("00");
  const [seconds, setSeconds] = useState("00");

  function diffInSeconds(startDate: Date): number {
    const now = new Date();
    const diffInMilliseconds = now.getTime() - startDate.getTime();
    const diffInSeconds = Math.floor(diffInMilliseconds / 1000);
    return diffInSeconds;
  }

  useEffect(() => {
    let interval: number;

    if (startTimer != 0) {
      const startDate = new Date()
      interval = setInterval(() => {
        const totalSeconds = startTimer ? startTimer * 60 : 0;
        const diff = diffInSeconds(startDate);
        const currentSeconds = totalSeconds - diff;
        const minutesAmount = Math.floor(currentSeconds / 60);
        const secondsAmount = currentSeconds % 60;

        if (diff > totalSeconds) {
          clearInterval(interval);
        } else {
          setMinutes(String(minutesAmount).padStart(2, "0"));
          setSeconds(String(secondsAmount).padStart(2, "0"));
        }
      }, 1000);
    }

    return () => {
      clearInterval(interval)
    }
  }, [startTimer]);

  function handleClick() {
    setStartTimer(10);
  }

  return (
    <div className={styles.container}>
      <div className={styles.countDownWrapper}>
        <span>{minutes[0]}</span>
        <span>{minutes[1]}</span>
        <div className={styles.separator}>:</div>
        <span>{seconds[0]}</span>
        <span>{seconds[1]}</span>
      </div>

      <button onClick={handleClick}>Start Count Down</button>
    </div>
  );
}

De antemão, quero dizer que o objetivo aqui não é explicar a lógica do componente CountDown.

Mas sim mostrar como usar context API para melhorar a manutenibilidade e legibilidade do seu código.

Nesse sentido, podemos perceber que apesar de ser um componente simples e pequeno, já começa a ficar um pouco difícil de entender tudo o que acontece no componente.

O componente acima gera o seguinte resultado:

React context - contador
Por simplicidade o contador sempre começa em 10

Criando um contexto

A ideia e mover toda a lógica que faz o count down funcionar para outro arquivo, só que para isso precisamos conhecer algumas funções:

Create Context: Cria um contexto:

const CountDownContext = createContext({})

Use Context: Faz uso de um contexto criado previamente:

const {info01, infor02, ...} = useContext(CountDownContext)

Provider: Responsável por disponibilizar todas as informações de um contexto para todos os componentes que sejam seus filhos.

export function App() {
  return ( 
    <CountDownContextProvider> // Provider
      <App />
    </CountDownContextProvider>
  )
}

Movendo a lógica

A princípio, criaremos um arquivo chamado countDownContext.tsx e moveremos toda a lógica para lá:

// countDownContext.tsx
import { createContext, useEffect, useState } from "react";

interface CountDownContextProps {
  minutes: string;
  seconds: string;
  start(): void;
}

export const CountDownContext = createContext({} as CountDownContextProps);

export function CountDownProvider({ children }: any) {
  const [startTimer, setStartTimer] = useState(0);
  const [minutes, setMinutes] = useState("00");
  const [seconds, setSeconds] = useState("00");

  function diffInSeconds(startDate: Date): number {
    const now = new Date();
    const diffInMilliseconds = now.getTime() - startDate.getTime();
    const diffInSeconds = Math.floor(diffInMilliseconds / 1000);
    return diffInSeconds;
  }

  useEffect(() => {
    let interval: number;

    if (startTimer != 0) {
      const startDate = new Date();
      interval = setInterval(() => {
        const totalSeconds = startTimer ? startTimer * 60 : 0;
        const diff = diffInSeconds(startDate);
        const currentSeconds = totalSeconds - diff;
        const minutesAmount = Math.floor(currentSeconds / 60);
        const secondsAmount = currentSeconds % 60;

        if (diff > totalSeconds) {
          clearInterval(interval);
        } else {
          setMinutes(String(minutesAmount).padStart(2, "0"));
          setSeconds(String(secondsAmount).padStart(2, "0"));
        }
      }, 1000);
    }

    return () => {
      clearInterval(interval);
    };
  }, [startTimer]);

  function start() {
    setStartTimer(10);
  }

  return (
    <CountDownContext.Provider
      value={{
        minutes,
        seconds,
        start,
      }}
    >
      {children}
    </CountDownContext.Provider>
  );
}
 

Finalmente, podemos fazer uso do nosso contexto, contudo, precisamos envolver os componentes que precisam do contexto com o provider.

// App.tsx
import styles from "./App.module.css";
import { CountDown } from "./CountDown";
import { CountDownProvider } from "./context/CountDownContext";
export function App() {
  return (
    <div className={styles.container}>
      <h1>
        <span>CountDown</span>
      </h1>

      <CountDownProvider> 
        <CountDown />
      </CountDownProvider>
    </div>
  );
}

Nesse caso, somente o CountDown precisa das informações do nosso contexto, então envolveremos somente o componente CountDown.

Como resultado, temos o nosso componente CountDown muito mais simples e claro. Sendo responsável apenas pela parte visual.

import { useContext } from "react";
import { CountDownContext } from "../context/CountDownContext";
import styles from "./styles.module.css";

export function CountDown() {
  const {minutes, seconds, start} = useContext(CountDownContext)
  

  return (
    <div className={styles.container}>
      <div className={styles.countDownWrapper}>
        <span>{minutes[0]}</span>
        <span>{minutes[1]}</span>
        <div className={styles.separator}>:</div>
        <span>{seconds[0]}</span>
        <span>{seconds[1]}</span>
      </div>

      <button onClick={start}>Start Count Down</button>
    </div>
  );
}

Da mesma forma, ao voltarmos ao navegador, o count down estará funcionando como esperado:

React context - contador
CoutDown

Conclusão

A princípio, pode parecer que o react context vai te dar mais trabalho, contudo, em um contexto mais complexo, o react context irá facilitar sua vide e muito.

Pois se quando você precisar mudar uma lógica, você não terá que sair procurando por todo seu código para fazer tal alteração.

Basta ir até o contexto que gerencia essa lógica e alterar uma única vez, conforme, vimos anteriormente.

Saiba mais