GraphQL no front-end (React e Apollo)
Tags: react, graphql, apollo, api
Published: 2022-12-26 16:40:09

Na última década, tecnologias como GraphQL mudaram a forma como construímos aplicações web e como elas se comunicam. O GraphQL oferece certos benefícios em relação ás APIs REST — vamos descobrir quais são.

---
Um dos principais benefícios do GraphQL, é a capacidade do cliente solicitar o que precisa do servidor e receber esses dados de forma exata e previsível. Sem muito esforço, pode=se extrair facilmente dados aninhados apenas adicionando mais propriedades ás nossas consultas, em vez de adicionar vários pontos de extremidade. Isso evita problemas como busca excessiva que podem afetar o desempenho.

Normalmente, para lidar com o GraphQL no lado do cliente, usamos o Apollo Client. Ele permite que os desenvolvedores definam, manipulem e disponibilizem consultas/multações em nossa aplicação. Ele também pode atuar como uma ferramenta de gerenciamento de estado em sua aplicação do lado do cliente.

Neste artigo, aprenderemos como lidar com atualizações em tempo real no lado do cliente usando GraphQL. Aprenderemos como fazer isso com os recursos do GraphQL, como atualização de cache, assinaturas e intrerface do usuário otimista. Também abordaremos como usar o Apollo como uma ferramenta de gerenciamento de estado, possivelmente substituindo o redux. Além disso, veremos como criar consultas GraphQL usáveis com Fragments e como usar diretivas Apollo para escrever consultas mais complexas.

## Instalação

Antes de começarmos, vamos apenas passar pela instalação e configurar nosso projeto. Vamos direto ao código. Para criar uma aplicação React, certifique-se de ter o Node.js instalado em seu computador. 

Vamos começar com nossa aplicação React executando este comando:

```
npx create-react-app react-graphql
```

Em seguida, vamos navegar para a pasta do nosso projeto no terminal:

```
cd react-graphql
```

Feito isso, instalaremos o Apollo usando esta linha:

```
npm i @apollo/client
```

## Atualização em tempo real no GraphQL

A capacidade de criar uma atualização em tempo real no lado do cliente, ajuda a melhorar a experiência do usuário no site, fazendo com que tudo pareça mais tranquilo. Imagine uma situação em que um usuário adiciona um novo item preenchendo um formulário e esse item é atualizando instantaneamente por ser adicionado á lista de itens na mesma página. Embora essa atualização em tempo real possa ser sincronizada com um servidor diretamente por meio de assinaturas, ou poder ser manipulada no front-end por meio de coisas como Optimistic UI, ou usando a função **update** no useMutation. Então, vamos para a implementação técnica.

## Atualizando o cache diretamente usando a função update no useMutation

useMutations são importados diretamente da biblioteca **@apollo/client** e nos ajudam a fazer mutações nos dados em nosso servidor.

Normalmente, podemos criar mutações com Apollo usando useMutations, mas além disso, o que faremos é usar a função **update** para atualizar nosso cache do cliente apollo, diretamente através de useMutation.

Neste exemplo abaixo, enviamos consultas ao servidor para obter uma lista de pets usando **useQuery** e fazemos uma mutação tendo um formulário para adicionar mais pets ao nosso servidor usando **useMutation**. O problema que teremos é que quando um novo animal de estimação é adicionado ao servidor, ele não é adicionado à lista de animais de estimação (no navegador) imediatamente, a menos que a página seja atualizada. Isso faz com que a experiência do usuário nesta seção da aplicação pareça quebrada, especialmente porque a lista de animais de estimação e o formulário estão na mesma página. Você pode visualizar o exemplo no [vimeo]([Veja o exemplo no Vime](https://vimeo.com/610757694)).

```jsx
import React, { useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/client";
import Loader from "../components/Loader";
import PetSection from "../components/PetSection";

//ALL_PETS usa gql de @apollo/client para nos permitir enviar consultas aninhadas

const ALL_PETS = gql`
  query AllPets {
    pets {
      id
      name
      type
      img
    }
  }
`;

// NEW_PET usa gql de @apollo/client para criar mutations
const NEW_PET = gql`
  mutation CreateAPet($newPet: NewPetInput!) {
    addedPet(input: $newPet) {
      id
      name
      type
      img
    }
  }
`;
function Pets() {
  const initialCount = 0;
  const [count, setCount] = useState(initialCount);
  const pets = useQuery(ALL_PETS);
  const [createPet, newPet] = useMutation(NEW_PET);
  const [name, setName] = useState("");
  const type = `DOG`;
 
  const onSubmit = (input) => {
    createPet({
      variables: { newPet: input },
    });
  };

  // esta função aciona a ação de envio chamando a função onSubmit acima dela
  const submit = (e) => {
    e.preventDefault();
    onSubmit({ name, type });
  };

// Se os dados estiverem carregando, exibimos o componente <Loader/>instead
  if (pets.loading || newPet.loading) {
    return <Loader />;
  }

// percorre os dados dos animais de estimação para obter cada animal de estimação e exibi-los com props usando o componente <PetSection>.
  const petsList = pets.data.pets.map((pet) => (
    <div className="col-xs-12 col-md-4 col" key={pet.id}>
      <div className="box">
        <PetSection pet={pet} />
      </div>
    </div>
  ));

  return (
    <div>
      <form onSubmit={submit}>
        <input
          className="input"
          type="text"
          placeholder="pet name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
        <button type="submit" name="submit">
          add pet
        </button>
      </form>
      <div>
        {petsList}
      </div>
      
    </div>
  );
}
export default Pets;

```

Usar a função **update** no hook **useMutation**, nos permite atualizar diretamente nosso cache lendo e gravando nosso arquivo **ALL_PETS**. Imediatamente pressionamos o botão enviar, os dados são adicionados á lista de animais de estimação no cache, alterando **ALL_PETS**. Isso nos permite atualizar nosso cache do lado do cliente imediatamente com dados consistentes.

```jsx
import React, { useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/client";
import Loader from "../components/Loader";
import PetSection from "../components/PetSection";

//ALL_PETS usa gql de @apollo/client para nos permitir enviar consultas aninhadas
const ALL_PETS = gql`
  query AllPets {
    pets {
      id
      name
      type
      img
    }
  }
`;

// NEW_PET usa gql de @apollo/client para criar mutations
const NEW_PET = gql`
  mutation CreateAPet($newPet: NewPetInput!) {
    addedPet(input: $newPet) {
      id
      name
      type
      img
    }
  }
`;

function ThePets() {
  const initialCount = 0;
  const [count, setCount] = useState(initialCount);
  const pets = useQuery(ALL_PETS);

  //Em seguida, usamos useMutation e update() para atualizar nosso ALL_PET

  const [createPet, newPet] = useMutation(NEW_PET, {
    update(cache, {data: {addedPet}}) {
      const allPets = cache.readQuery({query: ALL_PETS})
      cache.writeQuery({
        query: ALL_PETS,
        data: {pets: [addedPet, ...allPets.pets]}
      })
    }
  });
  const [name, setName] = useState("");
  const type = `DOG`;
 
  const onSubmit = (input) => {
    createPet({
      variables: { newPet: input },
    });
  };

  //Lida com o envio de Pets que eventualmente aciona createPet por meio de onSumit

  const submit = (e) => {
    e.preventDefault();
    onSubmit({ name, type });
  };

  //Se os dados estiverem carregando, exibimos o componente <Loader/>

  if (pets.loading || newPet.loading) {
    return <Loader />;
  }

//percorre os dados dos animais de estimação para obter cada animal de estimação e exibi-los com adereços usando o componente <PetSection>

  const petsList = pets.data.pets.map((pet) => (
    <div className="col-xs-12 col-md-4 col" key={pet.id}>
      <div className="box">
        <PetSection pet={pet} />
      </div>
    </div>
  ));
  return (
    <div>
      <form onSubmit={submit}>
        <input
          className="input"
          type="text"
          placeholder="pet name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
        <button type="submit" name="submit">
          add pet
        </button>
      </form>
      <div>
        {petsList}
      </div>
      
    </div>
  );
}
export default ThePets;

```

Você pode visualizar o resultado no [vimeo](https://vimeo.com/610770120).

## Assinaturas no GraphQL

Com base nas funcionalidades, a assinatura no GraphQL é semelhante ás consultas. A principal diferença é que, enquanto as consultas são feitas apenas uma vez, as assinaturas são conectadas ao servidor e são atualizadas automaticamente quando há qualquer alteração nessa assinatura específica. 

Primeiro temos que instalar:

```
npm install subscriptions-transport-ws
```
Em seguida, vamos ao nosso index.js para importar e usá-lo.

```jsx
 import { WebSocketLink } from "@apollo/client/link/ws";

//configurando nossos soquetes da web usando WebSocketLink
const link = new WebSocketLink({
  uri: `ws://localhost:4000/`,
  options: {
    reconnect: true,
  },
});
const client = new ApolloClient({
  link,
  uri: "http://localhost:4000",
  cache: new InMemoryCache(),
});
```

> **Obs:** _`uri` no bloco de código diretamente acima é para nosso endpoint._

Em seguida, entramos em nosso componente e, em vez da consulta como fizemos acima, usaremos esta assinatura:

```jsx
import {  useMutation, useSubscription } from "@apollo/client";
//iniciar nossa assinatura no lado do cliente

const ALL_PETS = gql`
  subscription AllPets {
    pets {
      id
      name
      type
      img
    }
  }
`;
```
E ao invés de usar **useQuery**, acessaríamos nossos dados usando **useSubscription**.

```jsx
 const getMessages = useSubscription(ALL_PETS);
```

## Optimistic UI 

Optimistic UI é um pouco diferente no sentido de que não está sincronizada com o servidor, como uma assinatura. Quando fazemos uma mutation, ao invés de esperar por outro pedido do servidor, ele usa automaticamente os dados já inseridos para atuailizar a lista de pets imediatamente. Então, assim que os dados originais do servidor chegarem, eles substituirão a Optimistic UI. Isso também é diferente de "Atualizar o cache diretamente usando a função **update** no **useMutation**", embora ainda vamos atualizar o cache neste processo.

```jsx
import React, { useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/client";
import Loader from "./Loader";
import PetSection from "./PetSection";

//ALL_PETS usa gql de @apollo/client para nos permitir enviar consultas aninhadas
const ALL_PETS = gql`
  query AllPets {
    pets {
      id
      name
      type
      img
    }
  }
`;

//NEW_PET usa gql de @apollo/client para criar mutations
const NEW_PET = gql`
  mutation CreateAPet($newPet: NewPetInput!) {
    addPet(input: $newPet) {
      id
      name
      type
      img
    }
  }
`;

function OptimisticPets() {
//Usamos useQuery para lidar com a resposta ALL_PETS e atribuí-la a animais de estimação
  const pets = useQuery(ALL_PETS);
//Usamos useMutation para lidar com mutations e atualizar ALL_PETS.
  const [createPet, newPet] = useMutation(NEW_PET
    , {
    update(cache, {data: {addPet}}) {
      const allPets = cache.readQuery({query: ALL_PETS})
      cache.writeQuery({
        query: ALL_PETS,
        data: {pets: [addPet, ...allPets.pets]}
      })
    }
  });;
  const [name, setName] = useState("");
  const type = `DOG`;
 //Lida com a mutation e cria a resposta optimistic
  const onSubmit = (input) => {
    createPet({
      variables: { newPet: input },
      optimisticResponse: {
        __typename: 'Mutation',
        addPet: {
          __typename: 'Pet',
          id: Math.floor(Math.random() * 1000000) + '',
          type: "CAT",
          name: input.name,
          img: 'https://via.placeholder.com/300',
        }
      }
    });
  };

//Aqui está o nosso envio aciona a função onSubmit
  const submit = (e) => {
    e.preventDefault();
    onSubmit({ name, type });
  };
//retorna o carregamento do componente quando os dados ainda estão sendo carregados
  if (pets.loading ) {
    return <Loader />;
  }
//percorre os animais de estimação e os exibe no componente PetSection
  const petsList = pets.data.pets.map((pet) => (
    <div className="col-xs-12 col-md-4 col" key={pet.id}>
      <div className="box">
        <PetSection pet={pet} />
      </div>
    </div>
  ));
  return (
    <div>
      <form onSubmit={submit}>
        <input
          className="input"
          type="text"
          placeholder="pet name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
        <button type="submit" name="submit">
          add pet
        </button>
      </form>
      <div>
        {petsList}
      </div>
      
    </div>
  );
}
export default OptimisticPets;
```
Quando o código acima chama **onSubmit**, o cache do Apollo Client armazena um objeto **addPet** com os valores dos campos especificados em **optimisticResponse**. No entanto, ele não substitui o cache principal **pets(ALL_PETS)** com o mesmo identificador de cache. Em vez disso, ele armazena uma versão separada e otmista do objeto. Isso garante que nossos dados em cache **permaneçam precisos** se o **optimisticResponse** estiverem errados.

O Apollo Client notifica todas as consultas ativas que incluem o arquivo **pets(ALL_PETS)**. Essas consultas são atualizadas automaticamente e seus componentes associados são renderizados novamente para mostrar nossos dados otimistas. Isso não requer nenhuma solicitação de rede, portanto, é exibido instantaneamente para o usuário.

Eventualmente, nosso servidor responde ao real da mutação para obter o objeto addPet correto. Em seguida, o cache do Apollo Client descarta nossa versão otimista do objeto addPet. Ele também substitui a versão em cache pelos valores retornados do servidor.

O Apollo Client notifica imediatamente todas as consultas afetadas novamente. Os componentes em questão são renderizados novamente, mas se a resposta do servidor corresponder ao nosso **optimisticResponse**, todo o processo ficará invisível para o usuário.

## Usando o Apollo como uma ferramenta de gerenciamento de estado no lado do cliente

Quando pensamos em ferramentas de gerenciamento de estado ou bibliotecas relacionadas ao react, o redux vem à mente. Curiosamente, o Apollo também pode atuar como uma ferramenta de gestão para o nosso estado local. Semelhante ao que temos feito com nossa API.

## Schemas e Resolvers no lado do cliente

Para conseguir isso, teremos que escrever esquemas no lado do cliente para definir o tipo de dados que queremos e como queremos que sejam estruturados. Para isso, criaremos Client.js onde definiremos os schemas e resolvers, após o que o tornaremos globalmente acessível em nosso projeto com o cliente Apollo.

Para esse exemplo, estarei estendendo o tipo **User** que já existe, para adicionar o height como um número inteiro. Os resolvers também são adicionados para preencher o campo height em nosso schema.

```jsx
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context'
import gql from 'graphql-tag'

//Estendendo o tipo User
const typeDefs = gql`
  extend type User {
    height: Int
  }
`

//Declarando nosso height dentro de nossos resolvedores no lado do cliente
const resolvers = {
  User : {
    height() {
      return 35
    }
  }
}
const cache = new InMemoryCache()
const http = new HttpLink({
  uri: 'http://localhost:4000/'
})
const link = ApolloLink.from([
  http
])

const client = new ApolloClient({
  link,
  cache,
  typeDefs,
  resolvers
})
export default client

client.js
```
Podemos então importar o **client** para o nosso **index.js**:

```jsx
import client from "./client"
import {
  ApolloProvider,
} from "@apollo/client";

//importing our client.js file into ApolloProvider
ReactDOM.render(
  <ApolloProvider client={client}>
    <Routing />
  </ApolloProvider>,
  document.getElementById("root")
);

index.js
```

Dentro do componente, ele vai usar assim. Adicionamos **@client** para indicar que a consulta é do lado do cliente e não deve tentar puxá-la do servidor.

```jsx
const ALL_PETS = gql`
  query AllPets {
    pets {
      id
      name
      type
      img
      owner {
        id
        height @client
      }
    }
  }
`;
```
Portanto, estamos extraindo dados do servidor e do cliente na mesma consulta, e eles estarão acessíveis por meio do hook **useQuery**.

## Criando Fragmentos e reutilizando consultas

Ás vezes, podemos precisar obter a mesma consulta em diferentes componentes. Portanto, em vez de codificá-la várias vezes, atribuímos essa consulta a algum tipo de variável e usamos essa variável.

Em nosso componente, apenas definimos o fragmento como **PetFields** sobre **Pet** (que é o tipo). Dessa forma, podemos apenas us´-ala em nossa **query** e **mutation**.

```jsx
const DUPLICATE_FIELD = gql`
  fragment PetFields on Pet {
      id
      name
      type
      img
  }
`
const ALL_PETS = gql`
  query AllPets {
    pets {
      ...PetFields
    }
  }
  ${DUPLICATE_FIELD}
`;
const NEW_PET = gql`
  mutation CreateAPet($newPet: NewPetInput!) {
    addPet(input: $newPet) {
        ...PetFields
    }
  }
  ${DUPLICATE_FIELD}
`;
```

## Diretivas Apollo

Ao fazer consultas, podemos querer ter algumas condicionais que removam ou incluam um campo ou fragmento se uma determina condição for atendida ou não. As diretivas padrão incluem:

**@skip:** Indica que um campo/fragmento deve ser ignorado se uma condição for atendida.

```jsx
const ALL_PETS = gql`
  query AllPets($name: Boolean!){
    pets {
      id
      name @skip: (if: $name)
      type
      img
    }
  }
`;
```
Em **$name** está um booleano que é adicionado como uma variável quando estamos chamando essa consulta. Está sendo utilizando com **@skip** para determinar quando exibir o campo name. Se verdadeiro, ele pula, se for false, resolve esse campo. 

**@includes** também funcionam de maneira semelhante. Se a condição for true, esse campo é resolvido e adicionando, e se for false, não é resolvido.

Temos também **@deprecated** que pode ser usado cpm schemas para retirar campos, onde você pode até adicionar motivos.

Também temos bibliotecas que nos permitem adicionar ainda mais diretivas, elas podem ser úteis ao criar coisas um tanto complicadas com o GraphQL.

## Dicas e truqyes com o uso do GraphQL Lodash dentro de suas consultas

GraphQL Lodash é uma biblioteca que pode nos auxiliar em uma consulta de forma mais eficiente, mais como uma forma avançada das diretivas Apollo.

Ele pode ajudá-lo a consultar seu servidor de uma forma que retorne dados de forma mais organizada e compacta. Por exemplo, você está consultando **title** de **films** assim: 

```jsx
films {
  title
}
```
E retorna os títulos de filmes como objetos em uma array:

```jsx
"films": [
    {
      "title" : "Prremier English"
    },
    {
      "title" : "There was a country"
    },
    {
      "title" : "Fast and Furious"
    }
    {
      "title" : "Beauty and the beast"
    }
]
```

Mas, quando usamos a diretiva map de lodash, podemos percorrer a array de filmes para ter uma única array com todos os títulos como filhos diretos.

```jsx
"films": [  
  "Premier English",
  "There was a country",
  "Fast and Furious",
  "Beauty and the beast"
]
```

Outra que se mostra útil, é a diretiva **keyby**. Você pode enviar uma consulta simples como essa:

```jsx
people {
  name
  age
  gender
}
```

Resposta:

```jsx
"people" : [
  {
    "name":  "James Walker",
    "age": "19",
    "gender": "male"
  },
  {
    "name":  "Alexa Walker",
    "age": "19",
    "gender": "female"
  }, 
]
```

Vamos usar a diretiva **@_keyup** em nossa consulta:

```jsx
people @_(keyBy: "name") {
  name
  age
  gender
}
```
A resposta ficará assim:

```jsx
"people" : [
  "James Walker" : {
     "name":  "James Walker",
     "age": "19",
     "gender": "male"    
  }
  "Alexa Walker" : {
     "name":  "Alexa Walker",
     "age": "19",
     "gender": "female"
  }
]
```
Portanto, neste caso, cada resposta tem uma chave, que é a name da pessoa.

## Conclusão

Neste artigo, abordamos tópicos avançados para obter atualização de dados em tempo real usando a função **update()**, assinatura e Optimistic UI. Tudo em um pouco para melhorar a experiência do usuário.

Também abordamos o uso do GraphQL para gerenciar o estado no lado do cliente e a criação de consultas reutilizáveis ​​com fragmentos do GrahQL. Este último nos permite usar as mesmas consultas em diferentes componentes onde for necessário, sem ter que repetir tudo todas as vezes.

No final, passamos pelas diretivas Apollo e Grahql Lodash para nos ajudar a consultar nossos servidores de maneira mais rápida e melhor.