Executando verificação de segurança...
9

Entendendo de uma vez por todas a Context API

Um dos recursos do React que eu sempre tive mais dificuldade de entender é a context API. Isso se devia muito pela falta de um conteúdo no estilo "context API for dummies", que ensinasse do início ao fim de uma forma simples e objetiva. Embora a própria documentação do React já seja uma ótima fonte para aprender sobre a context API, ela não ensina de uma forma simples e direto ao ponto. O meu objetivo com essa postagem é destrinchar todo o básico da context API necessário para começar a usá-la da melhor maneira possível.

Índice


1. O que são contextos no React?

Contextos são muito parecidos com estados, pode-se dizer que são como primos. Todo contexto guarda um estado, mas ao invés de ser um estado de um determinado componente, é um estado compartilhado entre vários componentes. Estados podem ser compartilhados apenas para componentes filhos por meio de props, já contextos podem ser compartilhados por qualquer componente para qualquer componente.

Vamos exemplificar isso com o exemplo clássico para a utilização de contexto: temas. Imagine que você tem uma página e quer adicionar um botão que modifica entre dark e light mode todos os componentes da página e ainda não conhece a Context API, naturalmente iria tentar resolver isso com estados, como no exemplo:

//MainPage.tsx
import { useState } from 'react'

export const MainPage = () => {
    const [theme, setTheme] = useState<'light' | 'dark'>('light')
    
    return (
        <button 
            type="button" 
            onClick={() => 
                setTheme(theme => theme === 'light' ? 'dark' : 'light')}
        > Trocar Tema </button>
        
        <Children theme={theme} />
    )
}
//Children.tsx

import { Head } from './Head'
import { NavBar } from './NavBar'
import { List } from './List'
import { Footer } from './Footer' 

interface IChildrenProps {
    theme: 'light' | 'dark'
}

export const Children = ({theme}: IChildrenProps) => {
    return (
        <div>
            <Head theme={theme} />
            <NavBar theme={theme} />
            <List theme={theme} />
            <Footer />
        </div>
    )
}

Perceba que todos os componentes recebem a prop theme, que é gerenciada pelo botão no componente pai MainPage. Nós apenas vimos dois componentes, mas quanto mais formos construindo e vendo o código dos componentes veremos que absolutamente todos recebem a prop theme. Isso é chamado de prop drilling e é um dos problemas resolvidos pela Context API.
Resumindo, contextos são estados que podem ser melhores gerenciados, sem a necessidade de se criar muitas props em componentes filhos.

2. Criando Contextos

Para criar um contexto é tão simples como um estado. Basta apenas utilizar o hook createContext do React.

//themeContext.tsx

import { createContext } from 'react'

export const ThemeContext = createContext<'light' | 'dark'>('light')

Da mesma forma que fazemos ao criarmos um estado com o useState, o argumento da função criateContext é o valor inicial do contexto. Esse hook retorna um objeto do tipo React.Context, que implementa a seguinte interface:

interface Context<T> {
    Provider: Provider<T>
    Consumer: Consumer<T>
    displayName?: string | undefined
}

3. Context Provider

O Componente Context.Provider retornado pelo hook createContext é um componente React que serve para prover o contexto para outros componentes. Seu funcionamento é em partes parecido com um setState, com a única diferença de que não é uma função que serve para modificar o contexto. É a partir desse componente que a comunicação entre vários componentes se faz possível sem o uso de props. Utilizando o mesmo exemplo dos temas que vimos inicialmente, teríamos:

// MainPage.tsx

import { useState } from 'react'
import { ThemeContext } from './themeContext'

export const MainPage = () => {
    const [theme, setTheme] = useState<'light' | 'dark'>('light')
    
    return (
        <button 
            type="button" 
            onClick={() => 
                setTheme(theme => theme === 'light' ? 'dark' : 'light')}
        > Trocar Tema </button>
        
        <ThemeContext.Provider value={theme}>
            <Children />
        </ThemeContext.Provider>
    )
}

Como pode ver no exemplo, um Context Provider recebe apenas a prop value, que é o valor que será passado ao contexto. Os componentes que irão receber esse contexto devem ser passados como componentes filhos do Provider, para assim compartilharem do mesmo contexto. Vale lembrar também, que cada vez que o value for mudado todos os componentes descendentes do Provider serão renderizados novamente.

4. Context Consumer

Como o próprio nome já sugere, esse componente serve para consumir o contexto passado. Todo componente que irá receber os valores de contexto deve utilizar esse componente. Continuando o exemplo inicial:

// Children.tsx

import { ThemeContext } from './themeContext'
import { Head } from './Head'
import { NavBar } from './NavBar'
import { List } from './List'
import { Footer } from './Footer' 


export const Children = ({theme}: IChildrenProps) => {
    return (
        <div>
            <ThemeContext.Consumer>
             {
                 theme => (
                     <Head theme={theme} />
                     <NavBar theme={theme} />
                     <List theme={theme} />
                 )
             }
            </ThemeContext.Consumer>
            <Footer />
        </div>
    )
}

Com a context API podemos utilizar o valor de contexto diretamente nos componentes Head, NavBar e List inclusive, eliminando totalmente a necessidade de props nesses componentes:

// Head.tsx

import { ThemeContext } from './themeContext'

export const Head = () => {
    return (
        <ThemeContext.Consumer>
        {
            theme => (
                <h1 className={theme}> My header </h1>
            )
        }
        </ThemeContext.Consumer>
    )
}

// NavBar.tsx

import { ThemeContext } from './themeContext'

export const NavBar = () => {
    return (
        <ThemeContext.Consumer>
        {
            theme => (
                <nav className={theme}> 
                    <a>My navbar</a> 
                </nav>
            )
        }
        </ThemeContext.Consumer>
    )
}

// List.tsx

import { ThemeContext } from './themeContext'

export const List = () => {
    return (
        <ThemeContext.Consumer>
        {
            theme => (
                <ul className={theme}> 
                    <li> My list <li>
                </ul>
            )
        }
        </ThemeContext.Consumer>
    )
}

Resumindo: um Context Consumer é um componente que possibilita o consumo do valor de contexto, recebendo como child uma função que tem como parâmetro o valor atual do contexto e deve retornar um objeto JSX válido.

5. useContext

A Context API do React conta também com um segundo hook, o useContext. Seu uso é basicamente igual ao Context.Consumer, com apenas a sintaxe mais simples e limpa. Por exemplo, poderíamos reescrever o nosso componente List da seguinte forma:

// List.tsx

import { useContext } from 'react'
import { ThemeContext } from './themeContext'

export const List = () => {
    const theme = useContext(ThemeContext)
    return (
        <ul className={theme}> 
            <li> My list <li>
        </ul>
    )
}

O valor retornado pelo hook é o valor do contexto provido pelo Provider mais próximo e, caso não exista um provider, retornará o valor padrão passado para o hook createContext. A Vantagem em consumir contextos com esse hook é a melhor legibilidade do código, que fica muito mais limpo com essa solução.

6. Considerações Finais

A Context API é muito simples e poderosa, contudo pode se tornar muito complexa caso você não pratique o seu uso. O motivo da qual a maioria das pessoas tem dificuldades com ela é simplesmente a falta de prática. Como tudo na programação, praticar é a chave para um melhor entendimento. Então pratique até entender mais e mais a fundo sobre o seu funcionamento e possibilidades.

Carregando publicação patrocinada...
1

Boa explicação. Em fim, como você disse, somente praticando para entender o "Context" desse poderoso recurso.

1
1
0