GraphQL es una de las tecnologías mas interesantes hoy en día por una buena razón. Permite a un cliente realizar consultas específicas, complejas y/o agregar consultas de datos, además es fácil de iniciar.
Si has querido aprender GraphQL y comenzar a aprovechar sus poderes, este es un excelente lugar para comenzar.
Este taller es para principiantes de GraphQL con una comprensión básica de React. Se enfoca en la implementación del lado del cliente del uso de GraphQL usando el cliente Apollo.
¡Bienvenido! Hoy aprenderemos cómo construir una aplicación React para consumir datos desde GraphQL utilizando y Apollo Client. ¡Empecemos! 🚀
Este taller le proporcionará una introducción breve pero completa sobre cómo recuperar y actualizar datos remotos desde un endpoint GraphQL, así como la administración del estado local utilizando Apollo Client en combinación con React. La experiencia con React no es obligatoria, pero lo más probable es que sea de mucha ayuda al ver este taller.
Si bien este curso se dirige claramente a los principiantes de Apollo y GraphQL, incluso alguien con bastante experiencia en Apollo podría aprender algo nuevo en la lección sobre cómo administrar el estado local.
Cada lección se centra en una característica o concepto específico y se puede ver de forma independiente. Aún así, todo el taller está cuidadosamente diseñado, por lo que todas las lecciones se combinan para crear una aplicación de libro de cocina. Comienza con la consulta de datos desde un endpoint GraphQL.
Después de eso, mejoramos la consulta utilizando variables GraphQL para filtrar resultados. Luego pasamos a las mutaciones, lo que nos permite actualizar los datos. Con las mutaciones, hay bastantes problemas a tener en cuenta.
Usando el estado de enlace de Apollo, incluso podemos extender el esquema remoto GraphQL con capacidades locales. Por último, pero no menos importante, exploramos un par de utilidades bastante útiles que se envían con Apollo, como la refetching y polling.
Espero que este taller te sea de utilidad y que lo disfrutes. 🙌🏻❤️
Si necesita hacer referencia a las diapositivas, puede verlasaquí.
Comenzaremos conociendo la estructura de nuestro repositorio lo cual nos va a facilitar el trabajo, luego con una guía sobre cómo agregar el cliente Apollo a un proyecto existente, luego seguiremos usando los componentes de Consulta y Mutación para obtener y manipular datos usando una API GraphQL.
También necesitarás instalar Apollo DevTools for Chrome. Si tienes todo esto, ¡entonces estamos listos para comenzar!
Estructura del proyecto
apollo-client-workshop-react
├── server
│ ├── models
│ ├── resolvers
│ ├── utils
│ ├── package.json
│ └── index.js
├── client
│ ├── // En este espacio contruiremos nuestro cliente
└── .nvmrc
└── LICENSE
└── README.md
Nuestro sevidor GraphQL necesita precargar unos datos de ejemplo previamente para su funcionamiento
# Move to server folder
$ cd server
# Install dependencies
$ npm install
# Load data
$ npm run seed
# Run server
$ npm run start:slow
Nota: La base de datos almacena dos archivos JSON almacenados en /tmp/recipes.json
y /tmp/ingedients.json
.
00-start
Tu punto de partida01-setup
Configuración de Apollo Boost y React Apoll02-query
Escribiendo componentes de consulta03-dynamic-queries
Asignado variables a nuestra consulta04-mutations
Escribiendo componentes de mutación05-schema-extending
Modificando el schema en el cliente06-refetch
Obtener datos manualmente en intervalos
Primero iniciemos un proyecto React usando npx y Create React App:
$ npx create-react-app client
A continuación, puede grabar en el proyecto e iniciar un servidor de desarrollo local:
$ cd client && npm start
Aprenderemos cómo configurar el cliente Apollo utilizando Apollo Boost, conectarlo a un endpoint GraphQL y usar el proveedor Apollo para conectarlo a nuestra aplicación React. Además demostramos cómo usar un Apollo Consumer.
Luego de tener nuestra aplicación React creada inicialmente usando create-react-app
. Vamos a comenzar con Apollo Client, vamos a instarlar las dependencias necesarias para agregar los siguientes paquetes npm, GraphQL
, apollo-boost
y react-apollo.
Debemos estar dentro de la carpeta client
# Install dependencies
$ npm i -S graphql apollo-boost react-apollo
El paquete GraphQL es necesario para ciertas características, como el análisis de consultas GraphQL. Apollo-boost
es un paquete que viene con el ApolloClient
bien configurado para comenzar rápidamente. Por último, pero no menos importante react-apollo
, integra Apollo con React proporcionando múltiples componentes y utilidades.
Simplifiqué el por defecto del archivo /client/src/App.js
.
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<div>Hello World!</div>
</div>
);
}
}
export default App;
Luego, importamos ApolloClient desde apollo-boost
e instanciamos un nuevo ApolloClient()
. La única opción obligatoria que debemos proporcionar es uri: para nuestro endpoint GraphQL. En este caso, estamos usando "http://localhost:4000/", ya que allí ya tengo un servidor GraphQL en ejecución.
import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "http://localhost:4000/"
});
Vamos a verificar que nuestro cliente funciona como se espera al solicitar datos de nuestro endpoint GraphQL mediante una consulta. Nuestro client
espera un objeto que contiene al menos la propiedad de query
.
Escribimos uno usando la notación de template tag. ¿Qué busca ahora? Nuestro backend es un libro de cocina que contiene recetas. Para empezar, podemos crear todas las recetas. Para cada uno de ellos, solicitamos el id
y el title
.
client
.query({
query: gql`
{
recipes {
id
title
}
}
`
})
Si observas, puedes ver que es necesario importar gql
, esta dependencia es un template literal string que analiza las consultas de GraphQL en el estandar AST. Las cadenas GraphQL son la forma correcta de escribir consultas en nuestro código, ya que pueden analizarse de forma estática utilizando herramientas como eslint-plugin-graphql. Sin embargo, las cadenas son un inconveniente de manipular, si está tratando de hacer cosas como agregar campos adicionales, combinar varias consultas entre sí u otras cosas interesantes.
Ahí es donde entra en juego este paquete: le permite escribir sus consultas con los literales de plantilla de ES2015 y compilarlas en un AST con la etiqueta gql
.
Para utilizarlo solo es necesario agregar la siguiente linea en el top de nuestro archivo:
import gql from "graphql-tag";
Volviendo a nuestro código, una vez que la consulta se resuelve, imprimimos los resultados.
client
.query({
query: gql`
{
recipes {
id
title
}
}
`
})
.then(result => console.log(result));
Como puede ver, una vez que cargamos la página, la consulta se ejecutó y nuestro resultado se registró en la consola. Hasta ahora vamos bien.
Ya que tenemos ReactApollo
disponible, veamos cómo podemos configurarlo y usarlo dentro de nuestra función de render. import { ApolloProvider } from "react-apollo",
. El ApolloProvider
requiere una instancia de Apollo {cliente}
En nuestro caso, tomamos el que ya inicializamos y una vez configurado el ApolloProvider
ahora pasamos el cliente por el árbol de rendering a través de una función de React context.
import { ApolloProvider } from "react-apollo";
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<div>Hello World!</div>
</div>
</ApolloProvider>
);
}
}
Ahora vamos a usar un ApolloConsumer
, donde podemos aprovechar esta configuración para usar nuestro cliente para hacer consultas más a fondo en nuestro árbol de renderizado de React.
import { ApolloProvider, ApolloConsumer } from "react-apollo";
En este caso, tomamos nuestra consulta existente y la ejecutamos dentro de una render prop de ApolloConsumer
.
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<div>Hello World!</div>
<ApolloConsumer>
{client => {
client
.query({
query: gql`
{
recipes {
id
title
}
}
`
})
.then(result => console.log(result));
return null;
}}
</ApolloConsumer>
</ApolloProvider>
);
}
}
Para cumplir con las expectativas de la API de React, devolvimos null.
Si bien ApolloConsumer
puede ser útil en algunos casos, la mayoría de las veces, utilizará el componente de Consulta/Mutación o componentes de orden superior, todos en ReactApollo.
Para mostrar los datos, primero tenemos que buscarlos. El componente Query
nos permite describir qué datos nos interesan y manejar automáticamente la obtención de nuestros datos. Una vez que recibimos los datos podemos procesarlos usando React. Dado que el componente Query
maneja la obtención de datos, debemos asegurarnos de que tratamos adecuadamente los casos de un estado de carga, así como el momento en que se reciben los errores de la API de GraphQL. En esta lección cubriremos ambos.
Vamos a comenzar con cambiar nuestro ApolloConsumer
por el componente Query
. El componente de Query nos permite obtener datos y proporciona los datos como una render prop. El componente de consulta tiene un prop obligatorio, query
. Para la consulta, volveremos a utilizar la etiqueta gql con una cadena de consulta dentro de ella. Finalmente tendremos nuestro código de esta forma:
import React, { Component } from "react";
import ApolloClient from "apollo-boost";
import gql from "graphql-tag";
import { ApolloProvider, Query } from "react-apollo";
const client = new ApolloClient({
uri: "http://localhost:4000/"
});
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<Query
query={gql`
{
recipes {
id
title
}
}
`}
/>
</ApolloProvider>
);
}
}
export default App;
El componente Query
utiliza el patrón de render props
para devolvernos los datos de la consulta. De acuerdo a este patrón el component child de query
debe ser precisamente una función.
{
({ data }) => {
if (data.recipes === undefined) return null;
return (
<ul>
{data.recipes.map(({ id, title }) =>
<li key={id}>{title}</li>
)}
</ul>
)
}
}
Desglosemos las cosas importantes que suceden aquí:
El componente Query
de Apollo toma un prop query requerida con una consulta GraphQL que ha sido analizada usando el gql
de graphql-tag
. Query
también toma una prop requerida children
que debería ser una función. La función recibe una propiedad a la cual nosotros le hacemos destructoring para obtener solo la propiedad data
, este objeto data
inicialmente está vacío, pero una vez finalizada la carga, contiene los resultados de nuestra consulta. Validamos que la data tenga recipes
adjuntas para mostralos en pantalla, de lo contrario return null
.
Funciona de maravilla.
En este momento, no sabemos por qué nuestras recipes
no están definidas, simplemente si no estan no las mostramos en pantalla, pero es probable que puedan estar cargandose o que ocurrió un error. Para ayudar con esto, el componente de query expone dos propiedades más, loading
y error
. Las cuales nos permiten ejecutar una UI diferente según el estado de la consulta.
{
({ data,loading, error }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Something went wrong</p>;
return (
<ul>
{data.recipes.map(({ id, title }) =>
<li key={id}>{title}</li>
)}
</ul>
)
}
}
Si actualizamos la aplicación web, podemos ver nuestra UI de loading hasta que finalice la carga y lleguen los datos reales. Para probar el error de la interfaz de usuario, detenemos el servidor. Como era de esperar, ahora vemos que el caso de error se muestra.
Antes de terminar esta lección sobre el componente de consulta, queremos refactorizar el código un poco.
- [-] Extrar el componente de consulta, crear un
recipes.js
. - [-] Crear una variable
QUERY
para tener nuestrogql
query. - [-] Agregar importaciones necesarias.
- [-] Actualizar
App.js
para funcionar con nuestro refactor.
GraphQL soporta parámetros para consultas vía variables. Nos permiten aportar argumentos dinámicos. A menudo queremos que estas variables dependan de las decisiones tomadas por un usuario. En esta sesión, veremos cómo implementar una variable de filtro basada en un elemento de checkbox en la UI utilizando el componente de consulta de Apollo.
Para filtrar recetas vegetarianas, nuestra receta acepta un argumento booleano, vegetarian
.
const QUERY = gql`
{
recipes(vegetarian: true) {
id
title
}
}
`
Una vez que la página se vuelve a cargar, podemos ver que ahora solo aparecen recetas vegetarianas.
En esta lección, queremos que el argumento vegetariano dependa de una casilla de verificación, que puede ser controlada por un usuario.
Antes de hacerlo, comencemos con algunos conceptos básicos. Usando la etiqueta de la plantilla gql
, podemos asegurarnos de que un query
debe proporcionarse con ciertas variables.
Primero declaramos un nombre para una consulta específica, usando la sintaxi de query
y luego el nombre. Entonces indicamos que la variable vegetarian
es de tipo Boolean!
. Al agregar un signo de exclamación (!
), declaramos esta variable como obligatoria.
const QUERY = gql`
query recipes($vegetarian: Boolean! {
recipes(vegetarian: $ vegetarian) {
id
title
}
}
`;
Para proveer variables al componente Query
, simplemente agregamos otra prop, variables
. Esta prop acepta un objeto con las variables de consulta como propiedades clave-valor. En nuestro caso, nos propusimos vegetarian: true
.
const Recipes = () => (
<Query query={QUERY} variables={{ vegetarian: true}}>
...
</Query>
)
Ahora, actualizamos el navegador y verificamos que solo vemos comidas vegetarianas.
A continuación, queremos implementar la casilla de verificación de la interfaz de usuario y hacer un seguimiento de su estado.
- [-] Crear un checkbox con el label vegetarian.
- [-] Capturar el evento
onChange
para este field. - [-] Actualizar el estado con el valor del checkbox.
- [-] Asignar el estado a la variable
vegetarian
del componenteQuery
. - [-] BONUS!! Usar React Hooks.
En esta lección, usaremos el componente Mutación para invocar una mutación de una API GraphQL. Además, cubrimos cómo actualizar la memoria caché local mediante consultas de recuperación, así como mejorar la experiencia del usuario cuando se trata de mutaciones. Al final, discutimos varios errores relacionados con las mutaciones y las posibles formas de abordarlos.
Para esta lección, vamos a comenzar con las tareas que tú debes terminar para poder avanzar. La idea es crear un un componente A�ddRecipe
que contenga dos campos de entrada, uno para el título y otro para indicar si la receta es una receta vegetariana.
- [-] Crear componenete
AddRecipe
. - [-] Agregar campo
title
al formulario. - [-] Agregar campo
vegetarian
de tipo checkbox al formulario. - [-] Actualizar el estado con los valores del formulario.
- [-] BONUS!! Usar React Hooks.
Si hace clic en el botón Agregar, no ocurrirá nada en este momento, excepto hacer que los campos se limpien.
Implementamos mutaciones básicas.
Vamos a enviar la nueva información de la receta(formulario que debimos crear previamente) a nuestro back-end GraphQL. Vamos a nuestro componente AddReecipe
y al principio importamos �mutation
import { mutation } from "react-apollo";
Luego envolvemos nuestro formulario con este componente. Tiene un prop obligatoria, que es mutation
. En nuestro caso, queremos agregar un addRecipe
ya que nuestro backend asi lo estableció.
render() {
return (
<Mutation mutation={ADD_RECIPE_MUTATION}>
...
</mutation>
)
}
Dado que todavía no tenemos tal mutación, necesitamos crearla. Al igual que con las consultas, vamos a utilizar el template tag gql
.
const ADD_RECIPE_MUTATION = gql`
mutation addRecipe($recipe: RecipeInput!) {
addRecipe(recipe: $recipe)
{
id
title
}
}
`
Ahora que tenemos nuestra mutación lista, usémosla. El componenet child de mutation
debe ser exactamente una función. Esto es llamado render prop. El llamado con la función de mutar que llamada addRecipe
de acuerdo a la definición del backend. El segundo argumento es un objeto que contiene un resultado de mutation
, así como el estado de loading
y error
.
render() {
return (
<Mutation mutation={ADD_RECIPE_MUTATION}> {(addRecipe, { loading, error })
...
Una vez que se envía el formulario, podemos usar nuestra función addRecipe
para desencadenar la �mutation
y pasar el objeto de la receta, que contiene la propiedad title
y vegetarian
.
{(addRecipe, { loading, error }) => (
<form
onSubmit={evt => {
evt.preventDefault();
addRecipe({
variables: {
recipe: { title: this.state.title, vegetarian: this.state.vegetarian }
}
});
}}
¿Terminamos? Aún no. A favor de un buen UX, también debemos indicar el estado de carga e informar al usuario, en caso de que se produzca un error. Esto es todo lo que necesitamos para implementar la mutación.
<div>
<button>Add Recipe</button>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
Podemos ir al navegador y hacer clic en el botón Add Recipe para enviar el formulario. Aunque estoy seguro de que nuestra mutación tuvo éxito, no vemos reflejado nuestra nueva receta en la lista.
Este es el causado porque la consulta que trae la lista de recetas se encuentra en otro componente. De ninguna manera indicamos que esta lista debería actualizarse una vez que la mutación haya finalizado. Podemos verificar rápidamente que nuestra mutación tuvo éxito solo con actualizar la página.
Ahora, queremos actualizar la lista con la mutación. Por lo tanto, podemos usar la prop refetchQueries
en el componente Mutation
. Esto acepta una serie de consultas, que se volverán a ejecutar una vez que la mutación haya tenido éxito. Vamos a proporcionar nuestra consulta de recipes
.
render() {
return (
<Mutation
mutation={ADD_RECIPE_MUTATION}
refetchQueries={[
{
query: gql`
query recipes {
recipes {
id
title
}
}
} `
]}
>
{(addRecipe, { loading, error })
...
Desafortunadamente, esto no funcionará, porque la consulta en el componente Recipes
acepta una variable vegetarian
y por lo tanto, es diferente.
Esto significa que debemos pasar exactamente la misma consulta, con exactamente las mismas variables. Si bien podríamos copiarlo y pegarlo ahora, en este punto, probablemente sea mejor si exportamos la consulta y la importamos en este archivo, así como en la receta.
export const QUERY = gql`
query recipes($vegetarian: Boolean!) {
recipes(vegetarian: $vegetarian) {
id
title
}
}
`;
Luego usamos las consultas de recetas dentro de la refetchQueries
, donde pasamos un objecto vegetarianos y otro que no lo son.
<Mutation
mutation={ADD_RECIPE_MUTATION}
refetchQueries={[
{ query: QUERY, variables: { vegetarian: false } },
{ query: QUERY, variables: { vegetarian: true } },
]}
>
Vamos a intentarlo. Añadimos una nueva receta. Luego veremos cómo se activan las refetchQueries
después de que la mutación haya finalizado con éxito. Esto, sin embargo, podría no ser la experiencia de usuario deseada que está buscando. Personalmente prefiero si el indicador de carga permanece activo hasta que se actualicen las refetchQueries
. Afortunadamente, este comportamiento es trivial de implementar simplemente agregando un prop awaitRefetchQueries
y configurándolo en true
.
<Mutation
mutation={ADD_RECIPE_MUTATION}
refetchQueries={[
{ query: QUERY, variables: { vegetarian: false } },
{ query: QUERY, variables: { vegetarian: true } },
]}
awaitRefetchQueries={true}
>
Vamos a actualizar la página. Luego añadimos otro plato vegetariano. Como puede ver, esta receta apareció en la lista al mismo tiempo que desaparecía el indicador de carga. Si activamos el filtro vegetariano, la lista se procesa instantáneamente, ya que ya hemos actualizado el caché usando refetchQueries
.
Al principio es molesto tener que lidiar con la cache que se genera con Apollo Client. Si desea comenzar de manera simple, he visto a los desarrolladores hacer esto un par de veces y es desactivar el caché de Apollo de forma predeterminada y solo usarlo explícitamente, en caso de que sus optimizaciones tengan un gran impacto en la experiencia del usuario.
Con la introducción de apollo-link-state
, Apollo introdujo por primera vez una forma de administrar el estado local a través de consultas y mutaciones de GraphQL. Esto se puede lograr utilizando la directiva @client
. En esta lección, aprovecharemos esta función para realizar un seguimiento de las recetas destacadas y almacenar la información en el localStorage
.
En esta lección, queremos extender nuestra aplicación para permitir a los usuarios marcar sus recetas favoritas. Por lo tanto, queremos agregar un campo isStarred
a la receta. Dado que nuestro esquema no admite este campo, planeamos mantener esta información únicamente en el cliente, almacenado unicamente en local.
Para consultar un campo solo en el cliente, podemos aprovechar el decorador @client
y agregarlo a un campo en una consulta o una mutación.
import gql from "graphql-tag";
export const GET_RECIPES = gql`
query recipes($vegetarian: Boolean!) {
recipes(vegetarian: $vegetarian) {
id
title
isStarred @client
}
}
`;
Una vez agregado, este campo nunca se consulta en el punto final remoto, sin embargo, necesitamos proporcionar un resolver para esto. En nuestro archivo app.js
, tendremos un objecto resolver
y para el tipo recipe
agregue un resolver isStarred
. Temporalmente vamos a devolver el valor false
para todos los recipes
.
const resolvers = {
Recipe: {
isStarred: parent => false
}
}
Luego podemos usar el objeto del resolver y pasarlo a la propiedad clientState
durante la inicialización de ApolloClient
.
const client = new ApolloClient({
uri: "http://localhost:4000/",
clientState: {
resolvers
}
});
Esto es todo lo que necesitamos para poder recuperar el valor isStarred
en nuestra lista de Recipes.js
. Procesamos una estrella junto a cada uno de los títulos, y el color debería cambiar, según el estado. Naranja, si la receta está marcada y gris si no lo está.
return (
<ul>
{data.recipes.map(({ id, title, isStarred }) => (
<li key={id}>
{title}
<span style={{ color: isStarred ? "orange" : "grey" }}>
★
</span>
</li>
Actualicemos la página y verifiquemos que todas las estrellas estén inactivas.
A continuación, necesitamos una mutación que nos permita actualizar el campo isStarred
de una receta. Por lo tanto, extendemos nuestros resolvers del lado del cliente con una mutación que vamos a llamar updateRecipeStarred
. Nuestro plan es almacenar un array de recetas destacadas en el localstorage
. Antes de actualizarlo, recuperamos la lista.
Mutation: {
updateRecipeStarred: (_, variables) => {
const starredRecipes = JSON.parse(localStorage.getItem("starredRecipes")) || [];
}
}
// Signature
// fieldName(obj, args, context, info) { result }
Para conocer la firma de los resolvers puede ir aquí
En caso de que la variable isStarred
se establezca en true
, añadimo el id
actual al localstorage. En caso de que esté configurado como false
, filtramos la lista actual de ID's del localstorage para guardar de nuevo los ID's sin el actual.
Mutation: {
updateRecipeStarred: (_, variables) => {
const starredRecipes = JSON.parse(localStorage.getItem("starredRecipes")) || [];
if (variables.isStarred) {
const addItem = JSON.stringify(starredRecipes.concat([variables.id]))
localStorage.setItem("starredRecipes", addItem);
} else {
const removeItem = JSON.stringify(starredRecipes.filter(recipeId => recipeId !== variables.id))
localStorage.setItem("starredRecipes", removeItem);
}
}
}
Al final, devolvemos un objeto con __typename
y el valor isStarred
. Esto es útil en caso de que un desarrollador quiera consultar el valor actualizado.
Mutation: {
updateRecipeStarred: (_, variables) => {
...
return {
__typename: "Recipe",
isStarred: variables.isStarred
};
}
}
Como ahora tenemos la mutación y sabemos cómo almacenamos las recetas destacadas, también puede actualizar el codigo del resolver de recipes
. En el resolver, verificamos si el array starredRecipes
incluye el ID de la receta parent.id
actual y devolvemos el resultado.
const resolvers = {
Recipe: {
isStarred: parent => {
const starredRecipes = JSON.parse(localStorage.getItem("starredRecipes")) || [];
return starredRecipes.includes(parent.id);
}
},
Hasta este punto solo estabamos preparando el terreno, ahora tenemos todo lo que necesitamos para comenzar a usar nuestra mutación. Por lo tanto, agregamos una nueva mutación al archivo src/components/Recipes.js
.
const UPDATE_RECIPE_STARRED_MUTATION = gql`
mutation updateRecipeStarred($id: ID!, $isStarred: Boolean!) {
updateRecipeStarred(id: $id, isStarred: $isStarred) @client
}
`;
Después de eso, importamos el componente Mutation
y envolvemos el span
con la estrella con él.
import { Query, Mutation } from "react-apollo";
Luego pasamos nuestro UPDATE_RECIPE_STARRED_MUTATION
. Dado que esta mutación afecta el resultado de nuestra consulta de recetas, proporcionamos los dos refetchQeries
.
<Mutation
mutation={UPDATE_RECIPE_STARRED_MUTATION}
refetchQueries={[
{ query: GET_RECIPES, variables: { vegetarian: false } },
{ query: GET_RECIPES, variables: { vegetarian: true } }
]}
awaitRefetchQueries={true}
>
Una vez más, establezca awaitRefetchQueries
en verdadero. Después de eso, reemplazamos el <span>
con un <button>
y agregamos un evento onClick
. Una vez que se hace clic, invocamos la mutación con el ID
de receta actual y el valor inverso !IsStarred
.
<Mutation
mutation={UPDATE_RECIPE_STARRED_MUTATION}
refetchQueries={[
...
]}
awaitRefetchQueries={true}
>
{
(updateRecipeStarred, { loading, error }) => (
<button
className="star-btn"
style={{...}}
onClick={() =>
updateRecipeStarred({
variables: { id, isStarred: !isStarred }
})
}
>
★
</button>
)
}
</Mutation>
A continuación, agregamos una clase llamada star-btn
. Para asegurarme de que la estrella se vea realmente bien, agregué la clase al archivo index.html
.
<style>
.star-btn {
position: absolute;
padding: 0;
margin: 0 0 0 0.3rem;
height: 1.4rem;
line-height: 1.4rem;
background: none;
border: 0;
font-size: 1rem;
outline: 0
}
.star-btn:hover {
background: none;
opacity: 0.8;
}
.star-btn:focus {
box-shadow: none;
}
@keyframes inflate {
0% {
font-size: 1rem;
margin-left: 0.3rem;
}
100% {
font-size: 1.4rem;
margin-left: 0.1rem;
}
}
</style>
Dependiendo del estado loading
, agregamos la animation
. Por último, pero no menos importante, renderizar texto en caso de que se produzca un error
. Ahora, deberíamos poder comenzar nuestras recetas.
(updateRecipeStarred, { loading, error }) => (
<button
className="star-btn"
style={{
color: isStarred ? "orange" : "grey",
animation: loading ? "inflate 0.7s ease infinite alternate" : "none"
}}
onClick={() =>
updateRecipeStarred({
variables: { id, isStarred: !isStarred }
})
}
>
★ {error && "Failed to update"}
</button>
)
Actualizamos la página y le damos una oportunidad. Voila, funciona como un amuleto. Incluso funciona después de comprobar el filtro vegetariano.
En esta lección, primero exploraremos cómo recuperar manualmente los datos de una consulta ya ejecutada para recibir el estado más reciente de nuestros datos. Más tarde, utilizamos el polling para recuperar el último estado en un intervalo de tiempo. El polling puede ser una herramienta simple y efectiva para proporcionar una experiencia casi en tiempo real sin la sobrecarga de configurar una solución WebSocket.
Hasta ahora, utilizamos data
, loading
, error
desde la función render prop de un componente de Query
. Hay un par de utilidades más que vienen con este objeto. Uno de ellos es refetch
.
<Query query={GET_RECIPES} variables={{ vegetarian: checked.vegetarian }}>
{
({ data, loading, error, refetch }) => {
if (loading) return <p>Loading…</p>;
if (error) return <p>Something went wrong</p>;
return (
...
)
}
</Query>
Una vez invocado, se volverá a ejecutar la consulta. Vamos a intentarlo agregando un <button>
para actualizar las recetas.
<React.Fragment>
...
<button onClick={() => refetch()}> Refresh Recipes</button>
</React.Fragment>
Una vez implementado, agregamos un nuevo elemento en otro navegador, volvemos a nuestro anterior tab y ejecutamos la recuperación presionando el botón. Como era de esperar, ahora vemos la nueva receta que se está agregando. Otros ejemplos de utilidades que vienen dentro del render prop del componente Query
son: network status, obtener más datos con pagination o iniciar y detener el polling.
Dicho esto, la forma más fácil de lograr el polling para obtener nuevos resultados es usar la prop pollInterval
en el propio componente de consulta. De forma predeterminada, está desactivado, pero si proporciona un número como {3000}
, el componente volverá a ejecutar la consulta cada tres segundos.
<Query
query={GET_RECIPES}
variables={{ vegetarian: checked.vegetarian }}
pollInterval={3000}
>
Puedes probar creando otra nueva receta en otra pestaña y como puedes ver en la pestaña anterior del navegador, se puede ver en pocos segundos esta nueva recenta sin hacer un web socket, el polling puede ser una herramienta simple y efectiva para proporcionar una experiencia en tiempo casi real.
Con esto llegamos al final de este taller basico sobre Apollo Client
¡Espero que esto haya sido útil y/o te haya hecho aprender algo nuevo!
- Cristian Moreno - FullStack JavaScript Developer - Github See also the list of contributors who participated in this project.
This project is licensed under Copy Right - see the LICENSE file for details