Taming network with redux-requests, part 5 - GraphQL

klis87

Konrad LisiczyƄski

Posted on July 23, 2020

Taming network with redux-requests, part 5 - GraphQL

In the previous part of this series we discussed data normalisation and how this process could be automated even in REST world.

In this part we will cover redux-requests usage with GraphQL.

Redux and GraphQL combination

Before analyzing the usage, let's answer one question first, why GraphQL with Redux? Didn't GraphQL killed Redux? Why should we bother if we have Apollo and Relay?

The short answer is, because comparing Redux and GraphQL is like comparing apples and oranges. GraphQL is the alternative way to communicate with servers (mainly to REST) and Redux is just state management library. If using Redux with REST is valid, it is also for GraphQL.

The longer answer is, why server communication should totally change the way we write frontend? Why refactoring Redux/React application should involve rewriting of everything to Apollo/Relay? Is this really justified to rewrite everything just because network communication changed? What if something new will appear, will we do this again?

Don't get me wrong now, I very like Apollo for example and inspired a lot from them, but personally I still prefer Redux. I like the concept of separating business logic from view layer and I like Redux simplicity. So, I don't want to start any war now, I will just say why some people might still use Redux even with GraphQL, keeping big appreciation for apollo client and relay:

  • like previously mentioned, UI separation from business logic is important in my view, for a start it makes for instance React components much cleaner, secondly replacing UI framework to something else won't involve business logic rewriting, also unit testing is much easier due to this separation
  • technology changes, we had REST hype, now we have GraphQL hype, tomorrow we will have something else, I favour driver approach, encapsulating network communication protocol somehow, so when you switch to something else, you don't need to rewrite most of the app but only the communication layer
  • Apollo and Relay advocates having fetching logic inside React components, while it might be convenient to do this sometimes, often it is problematic, for instance I had issues with refetching on resize event because component with useQuery rerendered and I had caching disabled
  • fetching logic connected with rendering is not always the most convenient, a resent blog post from React docs shows different problems and some strategies to improve performance for example, looking at these I asked myself, would they have those problems if they fetched queries on Redux layer in the first place? Then rendering phase would be independent so we wouldn't even need to think about such issues as they wouldnt't even exist

Probably there are more reasons for my preference, but the bottom line is this, people have different opinions and preferences. Some still prefer Redux, some prefer dedicated GraphQL clients, giving up Redux alltogether. I recommend you to see below examples, actually the way of writing is quite similar, the most important reason is keeping business logic on UI level vs Redux level.

Setup of GraphQL driver

First, you need to install the graphql driver:

$ npm install @redux-requests/graphql

Then, to use this driver, just import it and pass to handleRequests, like you would do with other drivers:

import { handleRequests } from '@redux-requests/core';
import { createDriver } from '@redux-requests/graphql';

handleRequests({
  driver: createDriver({ url: 'http://localhost:3000/graphql' }),
});

Basic usage of GraphQL driver

Let's assume we have the following GraphQL schema:

type Book {
  id: ID!
  title: String!
  author: String!
  liked: Boolean!
}

type File {
  filename: String!
  mimetype: String!
  encoding: String!
}

type Query {
  books: [Book!]!
  book(id: ID!): Book
}

type Mutation {
  editBook(book: Book!): Book
  singleUpload(file: Upload!): File!
  multipleUpload(files: [Upload!]!): [File!]!
}

In order to send a query, just do it in a similar fashion to other drivers. The only one thing really specific to GraphQL is a way you define your actions. Let's create an action to fetch books:

import { gql } from '@redux-requests/graphql';

const fetchBooks = () => ({
  type: 'FETCH_BOOKS',
  request: {
    query: gql`
      {
        books {
          id
          title
          author
          liked
        }
      }
    `,
    headers: {
      SOMEHEADER: 'SOMEHEADER',
    },
  },
});

As you see, there is nothing fancy here, you just write GraphQL. Notice we wrap it in gql tag. Currently it only trims queries, but in the future it could do other stuff, so it is recommended to wrap all your queries in gql, especially that it will hint most of code editors to properly highlight them. Also notice that it is possible to pass headers, which could be useful for authentication for instance.

Passing variables

Now, let's fetch a specific book, which requires using variables:

const fetchBook = id => ({
  type: 'FETCH_BOOK',
  request: {
    query: gql`
      query($id: ID!) {
        book(id: $id) {
          id
          title
          author
          liked
        }
      }
    `,
    variables: { id },
  },
});

Using GraphQL fragments

Notice Book properties repeated across those two queries. As you probably know, the answer for this problem is GraphQL fragment, which you can create like this:

const bookFragment = gql`
  fragment BookFragment on Book {
    id
    title
    author
    liked
  }
`;

const fetchBook = id => ({
  type: 'FETCH_BOOK',
  request: {
    query: gql`
      query($id: ID!) {
        book(id: $id) {
          ...BookFragment
        }
      }
      ${bookFragment}
    `,
    variables: { id },
  },
});

Mutations

Mutations are done like queries, just use GraphQL language:

const editBook = book => ({
  type: 'EDIT_BOOK',
  request: {
    query: gql`
      mutation($book: Book!) {
        editBook(book: $book) {
          id
          title
          author
          liked
        }
      }
    `,
    variables: { book },
  },
  meta: {
    mutations: {
      FETCH_BOOKS: data => ({
        books: data.books.map(v => (v.id === book.id ? book : v)),
      }),
      FETCH_BOOK: data => ({
        book: data.book && data.book.id === book.id ? book : data.book,
      }),
    },
  },
});

File uploads

Upload files according to GraphQL multipart request specification, which is also used by other GraphQL clients and servers, like Apollo, is also supported.

So, to upload a single file:

const uploadFile = file => ({
  type: 'UPLOAD_FILE',
  request: {
    query: gql`
      mutation($file: Upload!) {
        singleUpload(file: $file) {
          filename
          mimetype
          encoding
        }
      }
    `,
    variables: { file },
  },
});

... or, to upload multiple files:

const uploadFiles = files => ({
  type: 'UPLOAD_FILES',
  request: {
    query: gql`
      mutation($files: [Upload!]!) {
        multipleUpload(files: $files) {
          filename
          mimetype
          encoding
        }
      }
    `,
    variables: { files },
  },
});

So, you can do it exactly in the same way like other libraries supporting GraphQL multipart request specification.

Normalisation with GraphQL

Because normalisation is the core feature in redux-requests, you could use automatic normalisation for GraphQL too. For instance, assumming that book queries are normalized, you could refactor editBook like that

const editBook = book => ({
  type: 'EDIT_BOOK',
  request: {
    query: gql`
      mutation($book: Book!) {
        editBook(book: $book) {
          id
          title
          author
          liked
        }
      }
    `,
    variables: { book },
  },
  meta: {
    normalize: true,
  },
});

So, we replaced meta.mutations with meta.normalize. Actually if you have experience with apollo, you will notice that from user perspective normalisation works in the very same way!

What's next?

In the next part, we will cover optimistic updates.

💖 đŸ’Ș 🙅 đŸš©
klis87
Konrad LisiczyƄski

Posted on July 23, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related