Taming network with redux-requests, part 5 - GraphQL
Konrad LisiczyĆski
Posted on July 23, 2020
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.
Posted on July 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.