DEV Community

José Lázaro
José Lázaro

Posted on

Gerenciamento de estado com useContext()

Lidar com estados no React é uma tarefa complicada quando começamos a utilizar estados de componentes que são consumidos globalmente. Criar estados em um componente e passar seu valor de estado para filhos e netos pode tornar o gerenciamento um tanto quanto complexo até mesmo para aplicações menores. Aqui começamos a falar de conceitos mais específicos do React com o intuito de facilitar nosso processo de produção de aplicações. Dessa vez, falaremos do useContext(), ferramenta e conceito imprescindível para produção de aplicações escaláveis e de manutenção eficiente. Vamos.

Início de projeto

Iremos criar um simples ToDo que receberá atividades a serem executadas. Definiremos um contexto global e utilizaremos este conceito para que a aplicação funcione corretamente.

Abaixo você verá os meus diretórios da aplicação e o conteúdo de cada um. Neste artigo me limitarei a falar apenas acerca do conceito do estado, o conhecimento básico acerca da criação dos componentes subtende-se que o leitor já deva possuir.

Image description

Os componentes estão todos em diretórios individuais dentro do diretório components. Desse modo organizamos o nosso código de uma maneira mais eficiente.

Lidando com contexto

Para que possamos entender o context precisamos inicialmente conhecer o conceito do state.

Quando estamos produzindo uma aplicação, é inevitável que em algum momento comecemos a reutilizar constantes e estados, isso se dá pelo dinamismo de informações que estão dispostas no processo produtivo do desenvolvimento. Geralmente, lidar com uso de dados de forma global, principalmente no React, costuma ser confuso, já que quando lidamos com a utilização de vários elementos, o dado pode ficar confuso em meio a tantos parents da cadeia de componentes. Imagine só, definir um state em um elemento pai e sair distribuindo este valor para filhos e mais filhos na aplicação. Em algum momento a confusão deve tomar de conta de tudo. Para isso, criamos contextos, que são estados(states) definidos globalmente que podem ser utilizados em qualquer parte da nossa aplicação. Isso é bem mais prático do ponto de vista produtivo, já que só precisaremos lidar com o estado definido num arquivo separado. Sabendo disso, vamos começar a aplicar o useContext, ferramenta fundamental para entendermos o gerenciamento de estado em nossas aplicações.

Criando um contexto

Inicialmente iremos criar o contexto onde nossas constantes globais serão armazenadas. É daqui que iremos exportar o estado para utilizarmos em qualquer lugar que quisermos da nossa aplicação.

Para isso, criaremos um novo diretório chamado de context. Dentro dele, armazenaremos um novo arquivo com o formato .jsx de nome ContextTodo. Esse aquivo armazenará os estados que precisamos. Ficamos então com esta estrutura.

Image description

Após o arquivo ter sido criado, devemos começar executando alguns passos para a criação do nosso contexto. Confira abaixo.

Image description

Como de praxe, começamos importando o react. E como nova ferramenta, utilizaremos o hook useContext que importaremos também do react. Além de claro, nosso bom e velho useState.

Após importados, definiremos uma constante chamada de ContextTodo (elemento que armazena o nosso estado global) e criaremos um contexto na mesma através do hook react createContext().

Para gerenciarmos e definirmos o que acontece dentro deste contexto criado utilizaremos o Provider. O provider neste caso será uma constante que armazena uma arrow function, esta constante(TodoProvider) fará uso do nosso contexto. Sendo assim, a aplicação ficará toda contida dentro do nosso provider.

A utilização do {children} sendo utilizado como uma prop tem como intuito desconstruir os elementos que serão definidos ao longo da aplicação.

Definindo o provider

Após criada nossa constante TodoProvider, devemos começar a criar alguns métodos de gerenciamento de estado. Abaixo você pode ver a estrutura criada e a explicação de cada elemento.

Image description

iniciamos uma constante todos que armazenará um estado. Este estado é um array de objetos que armazenará simples tarefas de uma lista de afazeres. Cada elemento do array guarda um id, um title e um status de conclusão. sendo assim:

const [todos, setTodos] = useState([
        {id: 1, title: 'Ir ao supermercado', done: false},
        {id: 2, title: 'Ir para academia', done: false},
        {id: 3, title: 'Passear com o cachorro', done: false},
    ]);

Enter fullscreen mode Exit fullscreen mode

Após definido o estado principal do ToDo, criaremos uma nova constante saveTodo que salvará uma nova tarefa através de sua função. Esta função recebe o todo como parâmetro e cria uma nova constante newTodo que armazena desta vez uma nova tarefa dentro de um objeto, que posteriormente será armazenado dentro do nosso array de objetos definidos.

Além disso, a função saveTodo armazenará dentro do nosso estado todos o novo valor obtido através do newTodo.

Sendo assim:

const saveTodo = todo => {
        const newTodo = {
            id: todos.length + 1,
            title: todo.title,
            done: false,
        };
        setTodos([...todos, newTodo]);
    };

Enter fullscreen mode Exit fullscreen mode

Além disso, nosso TodoProvider retorna um novo elemento, o <ContextTodo.Provider></ContextTodo.Provider> A utilização do elemento .Provider não é um equívoco, trata-se de um componente que está contido no objeto Context. Ele permite que componentes consumidores a assinarem mudanças no contexto, ou seja, gerenciar o contexto definido.

Segundo a própria documentção do React: O .Provider contém uma prop value, ela pode ser passada para ser consumida por componentes que são descendentes deste Provider. Um Provider pode ser conectado a vários consumidores. Providers podem ser aninhados para substituir valores mais ao fundo da árvore. Todos consumidores que são descendentes de um Provider serão renderizados novamente sempre que a prop value do Provider for alterada.

Desse modo, passamos dentro do nosso provider dois valores: O primeiro trata-se da lista de tarefas definida na constante todos, e o segundo é a nossa nova tarefa savetodo.

Sendo assim, temos a seguinte estrutura:

return(
        <ContextTodo.Provider value={{todos, saveTodo}}>
            {children}
        </ContextTodo.Provider>
    );

Enter fullscreen mode Exit fullscreen mode

Como os filhos do nosso provider conseguem utilizar os valores que o provider armazena, iremos definir o children como sendo o nosso app. Sendo assim, utilizamos as chaves passando o children dentro do nosso <ContextTodo.Provider></ContextTodo.Provider>.

Utilizando nosso estado global no App

Depois de definido o Provider, precisamos executar este elemento dentro do nosso app, sendo assim, ele será o pai de toda a nossa aplicação neste caso em específico. A estrutura do nosso App.jsx fica da seguinte forma:

Image description

Como podemos observar, o nosso TodoProvider é o elemento principal e dentro dele contém nossos componentes criados posteriormente ToDoList e AddTodo.

Entendendo os componentes criados

ToDoListItem

Image description

O componente contém uma prop chamada todo, ele retorna uma div que contém o id, o título e o status da tarefa de cada um das tarefas passadas.

ToDoList

Image description

Analisando agora o componente ToDoList podemos ver a utilização do componente ToDoListItem descrito posteriormente.

Para que possamos utilizar o contexto global definido na constante ContextTodo, devemos criar uma nova e defini-la como sendo o elemento que irá receber o uso de um contexto. Neste caso, utilizamos a constante context para usar o contexto da ContextTodo. Conforme abaixo:

const context = useContext(ContextTodo);
Enter fullscreen mode Exit fullscreen mode

Após definido, utilizaremos a constante data para receber e renderizar posteriormente cada um dos elementos do todo. Sendo assim, criamos um novo método .map para criar cada um dos elementos da lista de tarefas através do componente descrito anteriormente ToDoListItem.

O método .map neste caso executa a seguinte lógica:

1 - Chama o componente <ToDoListItem>;
2 - Define uma key como sendo o id de cada uma das tarefas;
3 - cria um novo parâmetro chamado todo que recebe o objeto ativo no .map.

 const data = context.todos.map(todo=>(
        <ToDoListItem key={todo.id} todo={todo}></ToDoListItem>
    ));

Enter fullscreen mode Exit fullscreen mode

O ToDolist retorna após isso o elemento data que agora guarda um array de tarefas.

Após isso, podemos ver nossas tarefas sendo renderizadas na tela conforme mostra a seguir:

Image description

Adicionando novas tarefas

A aplicação está quase pronta, para finalizarmos precisamos apenas definir o local onde iremos submeter novos dados para a nossa lista de tarefas. E como submetemos coisas? Através de formulários! Sendo assim, editaremos o nosso componente AddTodo.

Abaixo você pode visualizar a estrutura do componente.

Image description

No nosso componente AddTodo começamos declarando a constante saveTodo que receberá o valor da constante saveTodo que está em nosso contexto global.

const {saveTodo} = useContext(ContextTodo);
Enter fullscreen mode Exit fullscreen mode

Logo após podemos declarar constantes com estados locais.

const [todo, setTodo] = useState();
Enter fullscreen mode Exit fullscreen mode

Definimos agora a nova função dentro de uma constante chamada handleFormSubmit. Essa função será responsável por adicionar o valor digitado num input na nossa lista de tarefas.

const handleFormSubmit = e => {
        e.preventDefault();
        saveTodo(todo);
    };
Enter fullscreen mode Exit fullscreen mode

o comando saveTodo(todo) executa a função presente em nosso contexto tendo como parâmetro nossa constante todo já declarada.

Logo após definimos uma função que armazenará o valor digitado no input dentro da nossa cadeia de elementos todo através da desestruturação.

const handleInputChange = e => {
        setTodo({
            ...todo,
            title: e.target.value,
        });
    };
Enter fullscreen mode Exit fullscreen mode

e para finalizar, retornamos o nosso formulário com o input e o botão para que as funções definidas sejam executadas quando as mudanças previstas acontecerem. No caso do input o onChange chama a função handleInputChange e o submit do formulário executa a função handleFormSubmit.

return(
        <form onSubmit={handleFormSubmit}>
            <input type="text" name="title" id="title" placeholder="Nova Tarefa..." onChange={handleInputChange}/>
            <button>ADICIONAR</button>
        </form>
    );
Enter fullscreen mode Exit fullscreen mode

Como podemos perceber, a utilização do useContext é muito importante para gerenciamento de dados dentro de aplicações react, pois facilita a cadeia organizacional dos elementos e distribui os dados de forma mais consistente. Existem outras alternativas para gerenciamento de estado, como utilizar a lib redux por exemplo, mas utilizar ferramentas da própria tecnologia é muito importante para entendermos os conceitos iniciais de funcionamento.

Quaisquer dúvidas sobre a temática sugiro consultar a própria documentação do React acerca desta temática.

A produção deste artigo tem primordialmente caráter didático, tendo sido totalmente inspirado no seguinte vídeo.

Top comments (0)