Colocated Fragments: Como organizar tus queries en React
Tags: react, javascript, graphql, spanish
Published: 2020-10-18 02:28:50

## ¿A quien va dirigido éste artículo?

Desarrolladores trabajando en un proyecto con [React](https://reactjs.org/) que consume datos de una API [GraphQL](https://graphql.org/) y que desean conocer una alternativa para organizar la definición de sus queries.

## Introducción

Existen múltiples formas para organizar queries en un proyecto con React, pero normalmente te encontrarás con alguna variación de los siguientes dos métodos:

- Guardar todos los queries en un archivo único o centralizado. Por ejemplo, [queries.ts](https://github.com/tenhsor1/tutorial-graphql-queries-examples/blob/single-file/src/queries.ts).
- Colocar las definiciones completas de los queries junto al componente padre que los consume. [Ejemplo](https://github.com/tenhsor1/tutorial-graphql-queries-examples/blob/no-fragments/src/pages/Post.tsx#L9).

A lo largo de este artículo nos enfocaremos en aprender una variación del segundo método en el cual colocamos los queries junto a los componentes padre que los ejecutan, y a su vez, a través de Fragments colocamos cada campo consumido junto a los componentes hijos que los consumen.

## ¿Qué es un Fragment?

Un Fragment no es más que una unidad reutilizable de información.

De la documentación oficial de [GraphQL](https://graphql.org/learn/queries/#fragments):

> Los Fragments te permiten construir un conjunto de campos para después incluirlos en los queries donde se necesiten.

### ¿Por qué son útiles los Fragments?

Usemos un proyecto de un Blog como ejemplo. Supongamos que tenemos un query de GraphQL `post` que se encarga de regresar una publicación de un blog incluyendo la información sobre su autor así como cada uno de los comentarios realizados en dicha publicación:

```graphql
// Sin Fragment
post(id: ID!) {
  id
  title
  content
  date
  author {
    id
    name
    image
    email
  }
  comments {
    id
    content
    date
    author {
      id
      name
      image
      email
    }
  }
}
```

Seguro notaste que solicitamos dos veces la misma información sobre usuarios (`id`, `name`, `image`, `email`). La primera sobre el autor del post y la segunda sobre los autores de los comentarios. Ahora veamos el mismo ejemplo, pero ahora utilizando Fragments:

```graphql
// Con Fragment
post(id: ID!) {
  id
  title
  content
  date
  author {
    ...Avatar
  }
  comments {
    id
    content
    date
    author {
      ...Avatar
    }
  }
}

fragment Avatar on User {
  id
  name
  image
  email
}
```

Como puedes ver, nombramos a nuestro Fragment `Avatar` e indicamos que sólo podrá ser utilizado por campos del tipo `User`. La manera de consumir Fragments es mediante el operador de GraphQL spread seguido del nombre del Fragment: `...Avatar`. Todos los campos definidos en él serán incluidos en la sección donde se mande a llamar.

Los Fragments por sí solos son útiles, pero al combinarlos con los componentes de React, se vuelven todavía más potentes.

## Colocated Fragments

De la [documentación](https://www.apollographql.com/docs/react/data/fragments/#creating-colocated-fragments) del cliente GraphQL Apollo:

> Un Colocated Fragment es igual a cualquier otro Fragment, excepto que está adjunto a un componente en particular de React, el cual utiliza los campos de dicho Fragment

Básicamente, es "colocar" la creación del Fragment junto al componente que va a consumir su información.

### Creando un Colocated Fragment

Utilicemos como ejemplo nuestro componente `Avatar` el cual se encarga de mostrar la información de un usuario.

Así quedaría nuestro Colocated Fragment:

```jsx
// Avatar.jsx
import gql from 'graphql-tag';

export const Avatar = ({ user }) => {
  return (
    <div>
      <a href={`/user/${user.id}`}>
        <h3>{user.name}</h3>
        <img src={user.image} />
      </a>
    </div>
  );
};

Avatar.fragments = {
  user: gql`
    fragment Avatar on User {
      id
      name
      image
    }
  `
};
```

En este ejemplo suceden 3 cosas importantes:

- Primero definimos un nuevo Fragment llamado `Avatar`. No existe alguna regla explícita para nombrarlos, pero para evitar colisiones, una buena alternativa o sugerencia es llamarlos con el mismo nombre del componente al que están enlazados.
- Después exportamos el Colocated Fragment mediante la creación de una nueva propiedad en el componente de React `Avatar` llamada `fragments`.
    - Esta forma de exportarlos viene de la propuesta en la documentación de [Apollo](https://www.apollographql.com/docs/react/data/fragments/#creating-colocated-fragments), sin embargo, esto es cuestión de gustos, solo asegúrate de tener una convención que se respete en todo el proyecto (Si utilizas typescript, puedes crear un nuevo tipo para forzar que tus componentes incluyan el atributo `fragments`).
- Por último, el componente consume la información mediante la prop `user`, la cual incluye los mismos campos que el Fragment: `id`, `image` y `name` . (si utilizas typescript, al final dejo un "paso por paso" sobre cómo generar un type automáticamente para tu prop basado en el Colocated Fragment).

### Consumiendo un Colocated Fragment

La magia de los Colocated Fragments la encontramos en el momento de consumirlos. Utilicemos como ejemplo un componente que se encargue de renderizar el encabezado de un `Post` y que utilice nuestro componente `Avatar` para mostrar la información del autor:

```jsx
// PostHeader.jsx
import gql from 'graphql-tag';
import { Avatar } from './Avatar';
export const PostHeader = ({ post }) => {
  return (
    <div>
      <Avatar user={post.author} />
      <Link to={`/post/${post.id}`}>
        <h1>{post.title}</h1>
      </Link>
    </div>
  );
};

PostHeader.fragments = {
  post: gql`
    fragment PostHeader on Post {
      id
      title
      author {
        ...Avatar
      }
    }
    ${Avatar.fragments.user}
  `
};
```

Analicemos lo que está sucediendo:

- Primero importamos el componente `Avatar` el cual es utilizado por `PostHeader` para renderizar la información del autor, pero al importar `Avatar` ¡también estamos importando sus Colocated Fragments por default mediante el atributo `fragments`!
- A su vez, creamos un nuevo Colocated Fragment para el componente `PostHeader` el cual se compone de campos individuales y por el campo `author` el cual utiliza el Colocated Fragment `...Avatar` para definir la información requerida. ¡Aquí podemos ver que la magia de la composición de React ahora también está disponible en nuestros queries!
- Después, hacemos accesible el Colocated Fragment del componente `Avatar` a través de la interpolación de strings de javascript: `${Avatar.fragments.user}`.
- Por último, le pasamos a `Avatar` el atributo de `author` (que proviene del Colocated Fragment de `PostHeader`) a través de su prop `user`.

Ahora, el Colocated Fragment de `PostHeader` puede ser consumido de la misma manera en que consumimos el del componente `Avatar`, a través de su atributo `fragments`.

**Creando un query utilizando un Colocated Fragment**

Es momento de usar nuestros Colocated Fragments para construir el query. Para éste ejemplo utilizaremos el hook `useQuery` de [@apollo/client](https://www.apollographql.com/docs/react/), pero tu puedes utilizar la librería de tu preferencia:

```jsx
// PostList.jsx
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
import { PostHeader } from '../components/PostHeader';

const POST_LIST_QUERY = gql`
  query PostList {
    posts {
      ...PostHeader,
    }
  }
  ${PostHeader.fragments.post}
`;

export const PostList = () => {
  const { loading, error, data } = useQuery(POST_LIST_QUERY);
  if (loading) {
    return <div>Loading...</div>;
  }
  if (error || !data) {
    return <div>An error occurred</div>;
  }
  return (
    <div>
      <div>
        {data.posts.map((post) => (
          <PostHeader post={post} />
        ))}
      </div>
    </div>
  );
};
```

El query es construido de la misma manera que lo hicimos para los Colocated Fragments de `PostHeader` y `Avatar`.

- Primero importamos el componente `PostHeader` el cual incluye el atributo `fragments`. Agregamos el Colocated Fragment mediante la interpolación de strings: `${PostHeader.fragments.post}` y lo consumimos dentro del cuerpo del query `posts` mediante `...PostHeader`.
- Nuestro query ahora incluirá todos los campos definidos en los Colocated Fragments de `Avatar` y `PostHeader`.
- Ejecutamos el query `POST_LIST_QUERY` mediante el hook `useQuery` de @apollo/client.
- Por último, el query `posts` regresa un arreglo. Iteramos el arreglo y pasamos a `PostHeader` cada uno de los elementos regresados por el query mediante la prop `post`.

Así, hemos logrado construir nuestro query exitosamente mientras mantenemos los datos requeridos unidos a los componentes que al final lo consumen.

## ¿Por qué utilizar Colocated Fragments?

Al utilizar Colocated Fragments, Nuestra capa de datos en GraphQL obtiene por automático alguno de los beneficios de los componentes de React:

- **Alta cohesión (High cohesion):** Los componentes de React por naturaleza tienden a tener una [alta cohesión](https://es.wikipedia.org/wiki/Cohesi%C3%B3n_(inform%C3%A1tica)), (las capas de renderizado, estilos y lógica suelen existir en el mismo archivo o carpeta). Cuando importamos un componente no debemos preocuparnos por implementar alguna de estas capas manualmente. Utilizando Colocated Fragments, tampoco debes preocuparte por cómo conseguir los datos necesaria para el componente. ¡Tu componente ahora incluye las capas de renderizado, estilo, lógica y datos!
- **Bajo acomplamiento (Low coupling):** El obtener una alta cohesión entre nuestro componente y los datos nos da el beneficio adicional de obtener un [bajo acoplamiento](https://es.wikipedia.org/wiki/Acoplamiento_(inform%C3%A1tica)#Bajo_acoplamiento) entre distintos componentes, que a su vez brinda una mayor **mantenibilidad**.

    Esto puede quedar más claro con un ejemplo: Digamos que nuestro componente `Avatar`, ahora necesita mostrar el nombre de usuario de Twitter. Así quedaría el cambio al utilizar Colocated Fragments:

    ```jsx
    export const Avatar = ({ user }) => {
      return (
        <div>
          <a href={`/user/${user.id}`}>
            <h3>{user.name}</h3>
            {/* 1. in order to get access to this twitter attr */} 
            <h4>{user.twitter}</h4>
            <img src={user.image} />
          </a>
        </div>
      );
    };

    Avatar.fragments = {
      user: gql`
        fragment Avatar on User {
    	  id
          name
    	  twitter // 2. we only need to add this here
          image
      	}
      `
    };
    ```

    Con Colocated Fragments solo necesitamos agregar el nuevo valor de `twitter` en la definición del Fragment y ¡listo! No necesitamos verificar que cada uno de los componentes que consumen `Avatar` le pasen éste nuevo atributo.

- **Composición:** Mediante el uso de Colocated Fragments, logramos construir nuestros queries de la misma manera en que construimos nuestros componentes de React, mediante [composición](https://reactjs.org/docs/composition-vs-inheritance.html). Cada fragmento de información es exportable y **reutilizable** por otros fragmentos de información o queries.

## Extra (typescript): Genera tus prop types automáticamente

Si utilizas typescript, hay un beneficio extra de utilizar Colocated Fragments: ¡Poder generar automáticamente los prop types de tus componentes basado en los campos de tu fragment!

Veámos como lograrlo con `yarn` (también es posible con `npm`)

- Instalamos las liberías requeridas por @graphql-codegen

    ```bash
    yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
    ```

- En la carpeta raíz de tu proyecto de React/typescript ejecuta:

    ```bash
    ./node_modules/.bin/graphql-codegen init
    ```

- Contesta las preguntas del CLI para generar el archivo de configuración:
    - **What type of application are you building?** React
    - **Where is your schema?** La url o el filepath de donde se encuentra tu schema de GraphQL
    - **Where are your operations and fragments?** El path regex donde se encuentran tus componentes de React. Ejemplo: `./src/**/!(*.d).{ts,tsx}`
    - **Pick plugins**: Selecciona `TypeScript` y `TypeScript Operations`
    - **Where to write the output**: El path donde se generarán los prop types. Por default en `src/generated/graphql.ts`
    - **Do you want to generate an introspection file?** n
    - **How to name the config file?** El nombre del archivo de configuración que será creado. Por default `codegen.yml`
    - **What script in package.json should run the codegen?** El nombre del script en `package.json` que se generará para que ejecutes cada vez que quieras actualizar los prop types. Yo utilizo: `graphql-types`
- Después de completar estas preguntas, se te habrá generado un archivo llamado `codegen.yml` en tu carpeta raíz como el siguiente:

    ```yaml
    overwrite: true
    schema: "<http://localhost:4000>"
    documents: "./src/**/!(*.d).{ts,tsx}"
    generates:
      src/generated/graphql.ts:
        plugins:
          - "typescript"
          - "typescript-operations"

    ```

- Y en tu `package.json` ahora deberás de tener una nueva línea en el objeto de `scripts`:

    ```json
    "graphql-types": "graphql-codegen --config codegen.yml"
    ```

- Vamos a probarlo. Ejecuta:

    ```bash
    yarn graphql-types
    ```

- Si todo fue creado exitosamente, deberás de ver un mensaje cómo:

    ```bash
    yarn graphql-types
    yarn run v1.22.4
    $ graphql-codegen --config codegen.yml
      ✔ Parse configuration
      ✔ Generate outputs
    ✨  Done in 2.18s.
    ```

- Después de este paso deberás encontrar un archivo `src/generated/graphql.ts` con todos los types generados a partir de tus Fragments y tu GraphQL Schema. Basado en nuestro ejemplo, obtenemos algo así:

    ```tsx
    export type User = {
      __typename?: 'User';
      id: Scalars['ID'];
      name: Scalars['String'];
      email?: Maybe<Scalars['String']>;
      image?: Maybe<Scalars['String']>;
      twitter?: Maybe<Scalars['String']>;
    };

    export type AvatarFragment = (
      { __typename?: 'User' }
      & Pick<User, 'id' | 'name' | 'image'>
    );
    ```

- Si puedes encontrar los types de tus Fragments, estás listo para comenzar a usarlos en tus componentes.

    ```tsx
    // Avatar.tsx
    import gql from 'graphql-tag';
    import React from 'react';
    import { AvatarFragment } from '../generated/graphql';

    export interface AvatarProps {
    	user: AvatarFragment
    }
    export const Avatar = ({ user }: AvatarProps) => {
      return (
        <div>
          <a href={`/user/${user.id}`}>
            <h3>{user.name}</h3>
            <img src={user.image} />
          </a>
        </div>
      );
    };

    Avatar.fragments = {
      user: gql`
        fragment Avatar on User {
    	  id
          name
          image
      	}
      `
    };
    ```

- Listo. Ahora cada que haya un cambio en tus Colocated Fragments, sólo tendrás que ejecutar `yarn graphql-types` y tus prop types se actualizarán automáticamente!

Por último, te dejo los links a las branches del ejemplo del Blog. Cada branch representa una forma diferente en la que puedes organizar tus queries:

- [Ejemplo con Colocated Fragments y types automáticos](https://github.com/tenhsor1/tutorial-colocated-fragments-examples/tree/typed-fragments)
- [Ejemplo con Colocated Fragments](https://github.com/tenhsor1/tutorial-colocated-fragments-examples/tree/untyped-fragments)
- [Ejemplo sin Colocated Fragments](https://github.com/tenhsor1/tutorial-colocated-fragments-examples/tree/no-fragments)
- [Ejemplo sin Colocated Fragments y todos los queries en un único archivo](https://github.com/tenhsor1/tutorial-colocated-fragments-examples/tree/single-file)

Happy composing!