DEV Community

Fran Agulto
Fran Agulto

Posted on

Using Fragments with WPGraphQL in Faust.js

In WPGraphQL, fragments are a feature of GraphQL in the GraphQL query languagethat allows you to define reusable selections of fields. Fragments allow you to group fields together and give them a name, which can then be used in multiple queries or mutations.

In this article, I will explain what they provide and how to use them in Faust.js. Even though we’re using Faust.js, you should be able to transfer this knowledge to any other using WPGraphQL.

Getting Started

To benefit from this post, you should be familiar with the basics of WordPress development, WPGraphQL, Faust.js, and the Apollo Client.

Steps for Local Development Set-up

WordPress Setup:

Set up a WordPress site on local, WP Engine, or any host of your choice

  1. Set up a WordPress site on local, WP Engine, or any host of your choice

  2. Install and activate the WPGraphQL plugin

  3. Install and activate the Faust.js plugin

  4. Install and activate the Advanced Custom Fields plugin

  5. Install and activate the WPGraphQL For Advanced custom fields plugin

Faust.js Setup

Please follow the instructions to set up Faust.js in the quickstart portion of the docs here.

The Benefits of Using Fragments

Code Reusability: Fragments enable you to define a set of fields once and reuse them across different queries or mutations. This helps in reducing code duplication and promotes a modular approach to defining GraphQL operations.

Query Organization: Fragments help organize complex queries by breaking them into smaller, more manageable parts. You can efficiently compose queries and maintain a clear structure by defining fragments for specific data structures or entities.

Readability and Maintainability: Fragments make GraphQL queries more readable and understandable. Separating the fields into reusable fragments makes it easier to grasp the structure of the data being queried. Additionally, when changes are required, you can update the fragment definition, and all the queries using that fragment will be automatically updated.

How to Create and Implement Fragments for Your WPGraphQL Queries

Now that we went over the benefits of using fragments, let’s create some.

In this example, I have a custom post type called Movies with the fields title, id, slug and uri that we want to request.

 

{
  movies {
    nodes {
      title
      id
      slug
      uri
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code is a WPGraphQL query with no fragments.

This is what it would look like if we made a fragment for the above query:

{
  movies {
    nodes {
      ...MovieFragment
    }
  }
}

fragment MovieFragment on Movie {
  title
  id
  slug
  uri
}
Enter fullscreen mode Exit fullscreen mode

Let’s break down how we created a fragment for our query.

Once we know what fields we want to pull out from our Movies custom post type, we start making our fragment with the keyword fragment followed by the name we choose, in this case, we call it MovieFragment.

The on Movie clause specifies this fragment is applicable to the Movie object type. When WPGraphQL returns a node of type Movie, it uses this fragment to determine what fields to return on the result.

Inside the fragment, there are several fields (title, id, slug, uri) that correspond to the fields of the Movie type.

Lastly, we take our fragment named MovieFragment and insert it into the main query block right under the nodes that the original query had with the fields instead of the actual fields and just precede it by using ... much like the JavaScript spread syntax followed by the name.

You can go to your WP Admin, navigate over to GraphiQL IDE, and create the same custom post type with the same fields. Copy my queries and try this yourself or go ahead and make your own.

Now that this is done, we get back the same results, but our code is much cleaner and organized in a reusable way. 

Using Fragments in Faust.js

Now that we know how to make and implement fragments, let’s explore how Faust.js uses them out of the box with its default examples and create some custom ones.

Default Faust.js Fragments

The first fragments we will explore are one of the defaults that come with Faust.js.  Let’s look at the front page template that renders when you visit the main path.  This file is located at wp-templates/front-page.js.

import { useQuery, gql } from "@apollo/client";
import * as MENUS from "../constants/menus";
import { BlogInfoFragment } from "../fragments/GeneralSettings";
import {
  Header,
  Footer,
  Main,
  Container,
  NavigationMenu,
  Hero,
  SEO,
} from "../components";

export default function Component() {
  const { data } = useQuery(Component.query, {
    variables: Component.variables(),
  });

  const { title: siteTitle, description: siteDescription } =
    data?.generalSettings;
  const primaryMenu = data?.headerMenuItems?.nodes ?? [];
  const footerMenu = data?.footerMenuItems?.nodes ?? [];

  return (
    <>
      <SEO title={siteTitle} description={siteDescription} />
      <Header
        title={siteTitle}
        description={siteDescription}
        menuItems={primaryMenu}
      />
      <Main>
        <Container>
          <Hero title={"Fran's Front Page"} />
          <div className="text-center">
            <p>This page is utilizing the "front-page" WordPress template.</p>
            <code>./wp-templates/front-page.js</code>
          </div>
        </Container>
      </Main>
      <Footer title={siteTitle} menuItems={footerMenu} />
    </>
  );
}

Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  query GetPageData(
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
  ) {
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;

Component.variables = () => {
  return {
    headerLocation: MENUS.PRIMARY_LOCATION,
    footerLocation: MENUS.FOOTER_LOCATION,
  };
};
Enter fullscreen mode Exit fullscreen mode

At the very top of this file, we import the fragment we will use named BlogInfoFragment from the fragments directory. The next thing to focus on is the bottom of the file:

Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  query GetPageData(
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
  ) {
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

On the template Component, we define a query property that we assign at the top of the query block is the Component.query syntax, a GraphQL query string used to fetch the template’s data in the Faust.js template hierarchy. This is a Faust.js convention that makes it easier to load data.

Next, we pass in our fragment, which contains the fields from the WP GenralSetttings object type of title and description. Notice the syntax being ${BlogInfoFragment}. This is a technique called string interpolation or template interpolation. It allows you to dynamically insert the content of the fragment variable into the query string at that specific location. This is equivalent to defining the fragment inline, which allows us to use the fragment in the actual query operation.

In this case, the ${BlogInfoFragment} is used to include the fragment definition within the query string. The Apollo Client uses the gql function to parse the query string and understand that the ...BlogInfoFragment syntax is referencing the fragment named BlogInfoFragment.

Fragments Directory

This fragment is located at fragments/GeneralSettings.js and is imported at the top of this file.

The GeneralSettings.js file looks like this:

import { gql } from '@apollo/client';


export const BlogInfoFragment = gql`
fragment BlogInfoFragment on GeneralSettings {
title
description
}
`;

Enter fullscreen mode Exit fullscreen mode

In this code block, we export the variable called BlogInfoFragment and use the gql client function from Apollo to define it. Then we create the fragment in the template literal and add the fields we want back in the fragment. In GraphQL, the template literal starts with the backtick character () and ends with another backtick. They are used to define multi-string values for queries and mutations. This is used to define the GraphQL fragment named BlogInfoFragment.

This is the directory in Faust.js which contains your fragment files that can be shared and reused when you import them into your components and queries.

The fragment directory is useful when you need a centralized location for all your fragments. This can be beneficial when multiple components share the same fragments or when you want to keep your component files less cluttered.

Colocated Fragments

Colocated fragments are what Faust.js uses in the majority of its components. Colocation refers to the practice of defining fragments in the same file or module as the component that uses them, keeping the fragment definition alongside the component’s code.

This is what we consider best practice within Faust.js for these reasons:

Code Organization: By colocating fragments in the same component that uses them, the code becomes more organized and maintainable.

Local Scope: Fragments defined within a component have local scope and are not exposed globally. This prevents naming collisions and reduces the likelihood of conflicts with other fragments defined elsewhere in the application.

Explicit Relationship: Colocated fragments establish an explicit relationship between the fragment and the component that uses it. When reading or modifying the component, developers understand the specific fragment(s) used, facilitating more manageable maintenance and updates.

Things to Consider/Best Practices

You could run into conflicts if you had two fragments with the same name, but different fields, it would cause an issue. For example:

# Component A
fragment PostFields on Post {
  title
}

# Component B
fragment PostFields on Post {
  id
}

query {
  posts {
    nodes {
      ...PostFields
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the query is using the PostFields fragment, but WPGraphQL does not know which version of PostFields to use since there are two with the same name. This results in an invalid query that will not execute.

This example is valid:

fragment PostTitle on Post {
  title
}

fragment PostId on Post {
  id
}

{
  posts {
    nodes {
      ...PostTitle
      ...PostId
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We give a unique name to our fragments ensuring each component’s fragment definition remains distinct and can be used without conflict in WPGraphQL operations.

We can even ask for the same fields in both, like so:

fragment SomeComponentOnTheTopOfThePage on Post {
  id
  title
  author {
    node {
      id
      name
    }
  }
}

fragment SomeComponentOnTheSidebar on Post {
  id
  title
  date
  featuredImage {
    node {
      id
      sourceUrl
    }
  }
}

{
  posts {
    nodes {
      ...SomeComponentOnTheTopOfThePage
      ...SomeComponentOnTheSidebar
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Both components need id and title so they both declare it. This might seem redundant but it is not. If one component no longer needs the title, it can take it out of its fragment, but the title will still be queried because the other fragment still asks for it.

Each component should query for what it specifically needs. However, components should not ask for fields they don’t actually need to satisfy another component’s needs.

Let’s shift our focus back to our wp-templates/front-page.js file:

Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  query GetPageData(
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
  ) {
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Let’s look at the bottom of the file. The colocated fragment is${NavigationMenu.fragments.entry}. The ${NavigationMenu.fragments.entry} syntax includes the entry fragment from the NavigationMenu fragment collection. It allows the fields defined in the entry fragment to be used in the query.

This fragment is part of the NavigationMenu object and is defined in the components/NavigationMenu.js file:

import classNames from 'classnames/bind';
import { gql } from '@apollo/client';
import Link from 'next/link';
import flatListToHierarchical from '../../utilities/flatListToHierarchical';
import styles from './NavigationMenu.module.scss';
import stylesFromWP from './NavigationMenuClassesFromWP.module.scss';

let cx = classNames.bind(styles);
let cxFromWp = classNames.bind(stylesFromWP);

export default function NavigationMenu({ menuItems, className }) {
  if (!menuItems) {
    return null;
  }

  // Based on https://www.wpgraphql.com/docs/menus/#hierarchical-data
  const hierarchicalMenuItems = flatListToHierarchical(menuItems);

  function renderMenu(items) {
    return (
      <ul className={cx('menu')}>
        {items.map((item) => {
          const { id, path, label, children, cssClasses } = item;

          // @TODO - Remove guard clause after ghost menu items are no longer appended to array.
          if (!item.hasOwnProperty('__typename')) {
            return null;
          }

          return (
            <li key={id} className={cxFromWp(cssClasses)}>
              <Link href={path ?? ''}>{label ?? ''}</Link>
              {children.length ? renderMenu(children, true) : null}
            </li>
          );
        })}
      </ul>
    );
  }

  return (
    <nav
      className={cx(['component', className])}
      role="navigation"
      aria-label={`${menuItems[0]?.menu?.node?.name} menu`}>
      {renderMenu(hierarchicalMenuItems)}
    </nav>
  );
}

NavigationMenu.fragments = {
  entry: gql`
    fragment NavigationMenuItemFragment on MenuItem {
      id
      path
      label
      parentId
      cssClasses
      menu {
        node {
          name
        }
      }
    }
  `,
};

Enter fullscreen mode Exit fullscreen mode

At the very bottom of the file are the fields we ask for in the fragment as follows:

 fragment NavigationMenuItemFragment on MenuItem {
      id
      path
      label
      parentId
      cssClasses
      menu {
        node {
          name
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

The actual fragment is defined in the child component (NavigationMenu.js) that uses it explicitly within its component. It is then reused into a parent component (front-page.js)that consumes it and renders it along with other fragments in the query.

Custom Fragment Examples

Now that we have an example of default fragments in Faust.js, let’s explore some custom ones in this repo.

Using the custom post types we made for this article, let’s create a query block and fragments for them.

The file we are looking at is located at components/MovieCard.js.

import { gql, useQuery } from "@apollo/client";
import Link from "next/link";

const GET_MOVIES = gql`
  query GET_MOVIES {
    movies {
      nodes {
        ...MovieFragment
      }
    }
    actors {
      nodes {
        ...ActorFragment
      }
    }
  }

  fragment MovieFragment on Movie {
    title
    id
    slug
    uri
  }

  fragment ActorFragment on Actor {
    title
    id
    slug
    uri
  }
`;

function MovieList() {
  const { loading, error, data } = useQuery(GET_MOVIES);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Movies</h2>
      <ul>
        {data.movies.nodes.map((movie) => (
          <li key={movie.id}>
            <Link href={`${movie.uri}`}>
              <a>{movie.title}</a>
            </Link>
          </li>
        ))}
      </ul>

      <h2>Actors</h2>
      <ul>
        {data.actors.nodes.map((actor) => (
          <li key={actor.id}>
            <Link href={`${actor.uri}`}>
              <a>{actor.title}</a>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default MovieList;

Enter fullscreen mode Exit fullscreen mode

Let’s focus on the top of the file where our WPGraphQL Query and Fragment is located:

const GET_MOVIES = gql`
  query GET_MOVIES {
    movies {
      nodes {
        ...MovieFragment
      }
    }
    actors {
      nodes {
        ...ActorFragment
      }
    }
  }

  fragment MovieFragment on Movie {
    title
    id
    slug
    uri
  }

  fragment ActorFragment on Actor {
    title
    id
    slug
    uri
  }
`;



fragment ActorFragment on Actor {
title
id
slug
uri
}
`;
Enter fullscreen mode Exit fullscreen mode

The MovieFragment and ActorFragment are defined inline within the GraphQL query using the fragment keyword. They are then referenced using the spread syntax (...) within the GET_MOVIES query to include the fragment fields in the response which are at the bottom of the query.

Nested Fragments

We not only included the Movie data in a fragment, but we also nested the Actor data fragment in the query block. This is useful for larger applications where you could have multiple nested fragments that go on and on.

Along with the main benefits of regular fragments, nested fragments also allow developers to have composition. This enables you to build up more complex fragments by including smaller fragments within them. You can nest fragments within other fragments, creating a hierarchy of reusable building blocks. This modular approach makes composing and customizing queries based on specific needs easier.

If you have a fragment that needs to be shared across multiple components, you can have these live in the fragments folder, as stated earlier in the article. The query would look like this instead:

const GET_MOVIES = gql`
  ${MovieFragment}
  ${ActorFragment}
  query GET_MOVIES {
    movies {
      nodes {
        ...MovieFragment
      }
    }
    actors {
      nodes {
        ...ActorFragment
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Then we would go into the fragments folder and create our fragments there. Once that is done, we would import them into the component using them.

Conclusion

I hope this article provided a better understanding of using fragments with WPGraphQL and Faust.js.

As always, super stoked to hear your feedback and any questions you might have on headless WordPress. Hit us up on our Discord channel!

Top comments (0)