Leonardo Maldonado Fullstack developer. JavaScript, React, TypeScript, GraphQL.

What’s new in Relay v11

5 min read 1575

Relay Logo

GraphQL is a technology that is being used by many companies today (like Facebook, for example). It is becoming an important alternative for building reliable, scalable, and high-performance APIs.

Facebook released a new version a few months ago and is making heavy use of React, GraphQL, and the newest version of Relay in their application. Facebook has been using Relay in production for a few years and it is helping them to have a more scalable, stable, and maintainable application.

In this post, we’ll cover the new version of Relay. We will explore how the newest version is working and how we can create more reliable and scalable React and GraphQL applications.

Relay Hooks

Relay is a powerful JavaScript framework for working with GraphQL in React applications. Relay is different from other GraphQL clients because it’s more structured and opinionated.

Relay helps to build more scalable, structured, and high-performance React and GraphQL applications. It makes data-fetching in GraphQL easy, relying on GraphQL best practices such as fragments, connections, global object identification, etc.

The newest version of Relay released on March 9th, 2021 and has a more developer-friendly API. The new version supports React Suspense, although it’s an experimental feature.

Relay Hooks are a new set of APIs for fetching and managing GraphQL data in React applications using React Hooks.

To start with the newest version of Relay, let’s first create a new React application using create-react-app and set up Relay:

npx create-react-app graphql-relay-example --template typescript

After creating our React application, we need to install a few packages to get started with Relay:

yarn add react-relay relay-runtime isomorphic-fetch

yarn add --dev @types/react-relay @types/relay-runtime graphql relay-compiler relay-compiler-language-typescript

Now, we create a file called environment.tsx, which is where we’re going to create our Relay environment. The best way to use Relay is to use a GraphQL API that’s compatible with Relay, it will follow the best practices and make it easier to implement some things such as pagination.

Inside our environment.tsx file, put the following code:

import {
  Environment,
  Network,
  RecordSource,
  Store,
  RequestParameters,
  Variables
} from "relay-runtime";
import fetch from "isomorphic-fetch";

function fetchQuery(operation: RequestParameters, variables: Variables) {
  return fetch('https://podhouse-server.herokuapp.com/graphql', {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-type": "application/json",
    },
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
    }).then((response: any) => {
      return response.json()
    })
}

const network = Network.create(fetchQuery);

const env = new Environment({
  network,
  store: new Store(new RecordSource(), {
    gcReleaseBufferSize: 10,
  }),
});

export default env;

Inside our index.tsx, we import the RelayEnvironmentProvider and pass our environment to it:

import React from "react";
import ReactDOM from "react-dom";
import { RelayEnvironmentProvider } from "react-relay/hooks";
import environment from "./environment";

ReactDOM.render(
  <RelayEnvironmentProvider environment={environment}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </RelayEnvironmentProvider>,
  document.getElementById('root')
);

Now, we have Relay set up on our project and we can start to use the newest version and see how it works.

Fetching with useLazyLoadQuery

The easiest way of fetching data with Relay is by using the useLazyLoadQuery. This hook will fetch data during render, turning it into not the most efficient way of fetching data but the simplest:

import { useLazyLoadQuery } from "react-relay/hooks";
useLazyLoadQuery(query, variables, options);

Here’s how the useLazyLoadQuery works:

  • query — You need this to pass your GraphQL query template literal
  • variables — Is an object containing values to fetch the query. For example, when you want to authenticate a user inside your application
  • options — Is an object that you define a few properties. fetchPolicy is used to determine if the data should be cached or not. fetchKey is used to force the reevaluation of the query
  • networkCacheConfig is an object that you can define to your cache config options

The useLazyLoadQuery should always be used inside a RelayEnvironmentProvider. The useLazyLoadQuery may suspend your data when a network request is in flight depending on which fetchPolicy you choose. Depending on the fetchPolicy selected you should make use of React Suspense for loading states in your application.

Check out an example of the useLazyLoadQuery hook:

import React from "react";
import graphql from "babel-plugin-relay/macro";
import { useLazyLoadQuery } from "react-relay/hooks";

const query = graphql`
  query SettingsQuery {
    currentUser {
      id
      _id
      email
    }
  }
`;

const Component = () => {
  const data = useLazyLoadQuery(
    query,
    {},
    {
      fetchPolicy: "store-and-network",
    }
  );

  return (
    <div>
      <h1>{data.currentUser.email}</h1>
    </div>
  );
};

export default Component;

Fetching with usePreloadedQuery

The usePreloadedQuery is the most recommended hook for fetching data with Relay. It implements the render-as-you-fetch pattern, a pattern that allows us to load the data that we need and render our component in parallel.

The usePreloadedQuery can be a bit confusing, so let’s clear up how this hook works:

  • usePreloadedQuery makes use of the useQueryLoader, which is another hook available on the new version of Relay
  • useQueryLoader is a hook for safely load queries. It will keep a query reference stored and dispose of it when the component is disposed
  • useQueryLoader is designed to be used with usePreloadedQuery
  • useQueryLoader returns a queryReference, a loadQuery callback, and a disposeQuery callback
  • We need to use the loadQuery callback from useQueryLoader first, which will store a query reference in React state
  • After that, we pass the queryReference to our usePreloadedQuery, which will allow us to fetch data earlier while not blocking rendering on our component

The usePreloadedQuery is the most powerful and recommended way of fetching data with Relay.

Check out an example of usage of the usePreloadedQuery hook:

import React, { useEffect } from "react";
import graphql from "babel-plugin-relay/macro";
import { useQueryLoader, usePreloadedQuery } from "react-relay/hooks";

const query = graphql`
  query UserQuery($_id: ID!) {
    user(_id: $_id) {
      id
      _id
      name
    }
  }
`;

const Component = () => {
  const [queryReference, loadQuery, disposeQuery] = useQueryLoader(query);

  useEffect(() => {
    loadQuery({ _id: _id }, { fetchPolicy: "store-or-network" });
    return () => {
      disposeQuery();
    };
  }, [loadQuery, disposeQuery, _id]);

  return (
    <React.Suspense fallback="Loading user...">
    {queryReference != null ? <UserComponent queryReference={queryReference} /> : null
    }
    </React.Suspense>
  );
};

const UserComponent = ({ queryReference }) => {
  const data = usePreloadedQuery(query, queryReference);
  return <h1>{data.user?.name}</h1>;
}

export default Component;

usePaginationFragment

One of the advantages of using Relay, making your GraphQL API compatible with Relay, and following the GraphQL specification is that it makes it very easy to implement some features such as pagination.

The usePaginationFragment is a hook that can be used for rendering a fragment and paginate over it:

import { usePaginationFragment } from "react-relay/hooks";
usePaginationFragment(query, variables, options);

Here’s how the usePaginationFragment works:

  • fragment – A GraphQL fragment template literal. The GraphQL fragment must have a @connection and @refetchable directive, otherwise, it will throw an error
  • fragmentReference – A fragment reference that Relay uses from read data for the fragment from the store

We can use the usePaginationFragment along with the usePreloadedQuery. Check out an example of usage of the usePreloadedQuery hook. First, we create our query and fragment:

const query = graphql`
  query ProductsQuery($name: String!) {
    ...SearchProducts_products @arguments(name: $name)
  }
`;

const fragment = graphql`
fragment SearchProducts_products on Query
@argumentDefinitions(
name: { type: "String" }
after: { type: "String" }
first: { type: "Int", defaultValue: 30 }
before: { type: "String" }
last: { type: "Int" }
)
@refetchable(queryName: "SearchProductsPaginationQuery") {
products(
name: $name
after: $after
first: $first
before: $before
last: $last
) @connection(key: "SearchProducts_products", filters: ["name"]) {
edges {
node {
_id
name
image
}
}
}
}
`; 

Now, inside our component, we pass our query to the useQueryLoader hook and inside the child component we use the usePreloadedQuery:

import React, { useEffect } from "react";
import graphql from "babel-plugin-relay/macro";
import { useQueryLoader, usePreloadedQuery } from "react-relay/hooks";

const Component = () => {
  const [queryReference, loadQuery, disposeQuery] = useQueryLoader(query);

  useEffect(() => {
    loadQuery({ name: name }, { fetchPolicy: "store-or-network" });
    return () => {
      disposeQuery();
    };
  }, [loadQuery, disposeQuery, name]);

  return (
  <React.Suspense fallback="Loading products...">
    {queryReference != null ? (<ProductComponent queryReference={queryReference} />) : null}
  </React.Suspense>
  );
};

const ProductComponent = ({ queryReference }) => {
  const query = usePreloadedQuery(query, queryReference);
  const { data } = usePaginationFragment(fragment, query);

  return (
    <div>
      {data.friends?.edges.map(({ node }) => (
      <div>{node.name}</div>
      ))}
    </div>
  );
};

export default Component;

useMutation

Mutations are a very important part of GraphQL. It is what allows us to create, update, and delete data.

useMutation is the new hook for executing mutations with Relay. It is a very simple and straightforward hook, with only two parameters:

  • mutation — A GraphQL mutation template literal
  • commitMutationFn — A function that will be called in instead. This function is optional, most of the time you are not going to need it

useMutation returns only two values:

  • commitMutation — A function that will execute the mutation
  • isInFlight — A value to check if the mutation is still in flight. You can use the commitMutation as many times as you want, so usually you can use multiple mutations in flight at once

Check out an example of the usePreloadedQuery hook:

import React from "react";
import graphql from "babel-plugin-relay/macro";
import { useMutation } from "react-relay/hooks";

const mutation = graphql`
  mutation SignInWithEmail($input: SignInWithEmailInput!) {
    SignInWithEmail(input: $input) {
      token
      success
      error
    }
  }
`;

const Component = () => {
  const [commitMutation, isInFlight] = useMutation(mutation);

  const onSubmit = () => {
    commitMutation({
      variables: {
        input: {
          email: email,
          password: password,
        },
      },
      onCompleted: ({ SignInWithEmail }) => {
        if (SignInWithEmail?.error) {
          return SignInWithEmail?.error;
        }
        updateToken(SignInWithEmail?.token);
      },
    });
  };

  return (
    <form onSubmit={onSubmit}>
      <input type="text" value={email} />
      <input type="password" value={password} />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Component;

Conclusion

The newest version of Relay brought a set of new APIs that will help us to build more scalable React and GraphQL applications. The usage of React Hooks can help us to build more modular and high-performance applications, making our code easier to understand and free of unexpected side effects.

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Leonardo Maldonado Fullstack developer. JavaScript, React, TypeScript, GraphQL.

Leave a Reply