Integrating Apollo GraphQL Client into a Redux application for local state - Part 3
March 04, 2018
This is part 3 of a 3 part post about a recent project I’ve made for Preact with JWT authentication using Apollo GraphQL boilerplate. Instead of patching together different concepts and technologies on a per project basis, I’ve made this helpful starter pack which is great for testing and prototyping JWT based auth with Apollo.
Full code can be found in a link at the bottom of this post.
- Part 1: Preact using Preact CLI, with Node based JWT authentication view here
- Part 2: Integrating Apollo’s GraphQL for server communication view here
- Part 3: Integrating Apollo’s GraphQL for client communication and data management [you are here]
Post overview
The previous 2 posts focused on setting up a Preact front end using Redux, and then introducing Apollo’s GraphQL to the Express server. This post will focus on integrating the client side of GraphQL using Apollo with the Preact app, and discuss if/how this can be used alongside Redux.
This post will cover:
- The basic setup of Apollo Client in React.
- A look into a little more advanced usage of Apollo by intercepting data before it’s added to a component, separating the UI and logic.
- How Redux fits in with Apollo’s GraphQL client considering [Redux was dropped] (https://www.apollographql.com/docs/react/2.0-migration.html#redux) from Apollo 2.0 in December 2017.
- How to manage local-only state which is not related to server requested data.
Technologies used
Apollo is a “family of technologies” which helps consume the GraphQL API in an application. The GraphQL API is pretty amazing once you try it out. It helps front end code easily request information in any desired format using a single API endpoint, removing the need for a verbose REST setup and the complexity involved in updating front end code due to back end API changes.
There are alternatives to Apollo, such as Relay, but I decided to go with Apollo as the following means more and, in my opinion, better documentation.
Apollo requires setup on server and client side. As this part will cover the client:
Apollo Client
The client side implementation of Apollo is used alongside almost any front end service, whether it be Javascript, React, Vue, iOS etc. It provides a way of generating the GraphQL query to send to the server, and links the query and returned data to view components as per the chosen front end library.
Setup of GraphQL client
In the first post in this series I used Redux and Redux Thunk middleware to structure the front end state management for the application. As mentioned before, this post looks into how to integrate Apollo’s GraphQL client, so let’s begin by installing some basic packages:
npm i apollo-client-preset apollo-link-context apollo-link-http graphql-tag graphql react-apollo
The above installs
apollo-client-preset
which is a useful package incorporating some of the default presets, but I’m also adding a few of the packages separately as these are needed to customise the setup.
// index.js
...
import { ApolloClient, InMemoryCache } from 'apollo-client-preset';
import { setContext } from 'apollo-link-context';
import { createHttpLink } from 'apollo-link-http';
import { ApolloProvider } from 'react-apollo';
export const store = createStore(
...
);
const authLink = setContext((_, {
headers
}) => {
const token = localStorage.getItem('authToken');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
};
});
const httpLink = createHttpLink({
uri: 'http://localhost:1138/graphql'
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
const Main = () => (
<ApolloProvider client={client}>
<Provider store={store}>
<App />
</Provider>
</ApolloProvider>
);
render(<Main />, document.body);
You may notice that the index.js file in my app contains a lot of setup code for different concerns like Preact, Redux, Thunks, even custom middleware, as well as the Apollo integration here. I’ve left it all in the same file so it’s easy to see exactly what dependencies are required, but you may wish to split these out into different files as the app grows. The Redux integration is pretty much the same with the creation of the store
constant and using alongside the Provider
component. Apollo introduces the ApolloProvider
component which needs to be wrap around the Redux store in order to use them both together (I’ll go into detail about Redux and Apollo working together later in this post).
There are 2 parts of the client setup which steer away from the preset/default setup. The first is that we are using the createHttpLink
function passed in from the apollo-link-http
package. This is used in order to specify your own GraphQL URL endpoint. The server-side setup of this endpoint was discussed at the end of the last post, and is the route which is protected by JSON Web Tokens.
The second part of custom setup is using the setContext
function passing in from the apollo-link-context
package. This is used to “set the context” of requests, allowing us to retrieve the JWT from local storage and set it in the request header object.
The http link and link context are concatenated and used in the main ApolloClient
function which is used in the app.
It’s also worth noting that this post uses Apollo 2.0, which means that integration with Redux is different than in version 1.0. Redux was recently dropped as a dependency, which is why we are having to use separate wrapper components with their own props for
client
andstore
.
Performing a basic query
Forgetting for one minute that we are integrating Apollo into Preact (or React, or any front-end framework), I want to show how a basic query can be performed independently:
// someFile.js
import gql from "graphql-tag"
// use the client constant defined in the above code snippet from index.js
client
.query({
query: gql`
query AllUsers {
name
dateOfBirth
}
`,
})
.then((data) => console.log(data))
.catch((error) => console.error(error))
This shows how a GraphQL query can be used without something like React/Preact, which is useful when not using a framework. We are still using Apollo’s implementation here which leverages some excellent caching and abstractions, however this will need to be integrated into Preact/React’s component architecture with the help of another package. Enter React Apollo.
React Apollo is used alongside the standard Apollo integration to give us the ApolloProvider
which is used to wrap our whole app in a component in index.js
, and also the graphql
function which allows us to create higher-order components that can execute queries and update the front-end of the app reactively based on the data in the Apollo local store. Here’s how a really basic query can be performed using the React Apollo functions:
// routes/admin/index.js
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
export class Admin extends Component {
...
render({ data }, {}) {
return (
<div>
{data.length > 0 &&
data.map(
user => user && (
<div>{user.name}</div>
)
)
}
</div>
);
}
}
// define query in a constant
const allUsersQuery = gql`
query {
allUsers {
refreshToken
email
}
}
`;
// use the query as the main parameter of the graphQL higher order function
export default graphql(allUsersQuery)(Admin);
I’ve made a component called Admin
which is passed the higher-order component that is returned from the graphql
higher-order function. This is similar to how the connect()
function in Redux works, so if you want to know more, check out the docs over at React for HOC’s.
This shows the major benefit of using GraphQL - we don’t need to perform REST style operations throughout different parts of our application in order to query data we need for each component. We simply link up a component to the
graphql
higher order function, giving the component access to the data queried from the server.
Separating UI and logic
While the above snippet is great for showing a basic query, it does not allow us to perform updates to the component before injecting the data, or use the data which is returned for more complex logic functionality in the same way we could do with something like Redux Thunk. When working with front-end frameworks, I like to keep the logic and UI as separated as possible, keeping it nice and clean when switching between functionality and UI development. With Redux Thunks this is easy because you’re able to run functionality steps within dispatch functions after data is received from an API. For example:
// routes/home/index.js
...
function mapDispatchToProps(dispatch) {
return {
saveTokens: (authToken, refreshToken) => {
dispatch(saveTokens(authToken, refreshToken));
}
};
}
// reducers/auth.service.js
export const saveTokens = (authToken, refreshToken) => dispatch => {
localStorage.setItem('authToken', authToken);
localStorage.setItem('refreshToken', refreshToken);
dispatch(
saveTokensToStore({
authToken,
refreshToken
}, jwtDecode(authToken))
);
};
As an application grows I find it much better to have dedicated files, which I call services (although some call them modules) set aside to handle application logic. In Apollo Client, this can easily be implemented by using the options.props
parameter of the graphql
function:
// routes/admin/index.js
export class Admin extends Component {
...
render({ newModifiedData }, {}) {
return (
<div>
{newModifiedData.length > 0 &&
newModifiedData.map(
user => user && (
<div>{user.name}</div>
)
)
}
</div>
);
}
}
const allUsersQuery = gql`
query {
allUsers {
refreshToken
email
}
}
`;
export default graphql(
allUsersQuery, // the query parameter
{ // the options parameter
props: ({ ownProps, data }) => {
// manipulate data which is to be passed into the component
let newModifiedData = someFunction();
return {
...data,
newModifiedData
};
}
}
)(Admin);
This options.props
enables the data returned from the GraphQL query to be modified, and used in other functions before being passed into the props of the component.
Using Apollo Client 2.0 with Redux, or not?
Now I’ve covered the basic setup, the next logical step I’d looked to learn was how to query data and store it locally. I think having a global store is one of the best things to happen to every day development of Javascript. Redux, for me anyway, has been a great way to keep control over large scale applications. One of the bug bearing questions I had when learning Apollo Client was how to store queried data, if this could be done with Redux alongside Apollo queries, or if there was a better solution.
The repo for this post was using Redux for the following, both of which I wanted to still be achieved in some way with Apollo and/or Redux:
- Storing data locally which was received from the server. This was done in the Redux store.
- Managing local global state which is not received from the server. This includes state such as “loading” state in the form of
isLoading
props for example, allowing the creating of loading messages, as well as other custom UI states like toggles etc.
As mentioned earlier, Redux is no longer used within Apollo Client like it was back in version 1.0 to store data, so here was my process of understanding the options available, bearing in mind at this point I was trying hard to make Redux work alongside Apollo Client 2.0 because Redux is used heavily in a lot of my projects.
// routes/admin/index.js
...
export class Admin extends Component {
...
render({ adminAllTokens }, {}) {
return (
<div>
{adminAllTokens.length > 0 &&
adminAllTokens.map(
user => user && (
<div>{user.name}</div>
)
)
}
</div>
);
}
}
const AllRefreshTokens = gql`
query {
allUsers {
refreshToken
email
}
}
`;
const gqlWrapper = graphql(AllRefreshTokens, {
props: ({ ownProps, data }) => {
// rather than modifying data and returning straight to component, I want to
// send to Redux store instead, so the data is available in the whole app
let newModifiedData = someFunction();
// ownProps contains the mapDispatchToProps function from Redux
ownProps.receivedAdminTokens(newModifiedData);
}
});
function mapStateToProps(state, ownProps) {
return {
adminAllTokens: state.auth.adminAllTokens
};
}
function mapDispatchToProps(dispatch) {
return {
receivedAdminTokens: tokens => {
dispatch(adminTokensReceived(tokens));
}
};
}
const reduxWrapper = connect(mapStateToProps, mapDispatchToProps);
export default compose(reduxWrapper, gqlWrapper)(Admin);
In order to clean up the code a little, I’ve use
compose()
from Redux, which runs the higher order functions in a chain, passing the returned values to the next function.
The idea with the above snippet was to get the data returned from the GraphQL query, and push it to the Redux store so the rest of the application could update. Having a local store hold queried data is something which Apollo is able to do for you (which I’ll cover shortly) but I wanted to see what it would mean to introduce Apollo 2.0 to existing Redux based project, and maintain 2 separate stores (Redux store and the Apollo store).
I thought it was going to be as easy as that, but it didn’t work. When the Apollo HOC receives new props, the Redux dispatch receivedAdminTokens()
is fired, which updates the store and sends new props to the Apollo HOC, starting the cycle again and causing an infinite loop.
From this, I found out about Apollo’s withApollo()
enhancer:
// routes/admin/index.js
...
export class Admin extends Component {
...
componentDidMount() {
const { client, receivedAdminTokens } = this.props;
client
.query({ query: AllRefreshTokens })
.then(({ data }) => {
let newModifiedData = someFunction();
receivedAdminTokens(newModifiedData);
})
.catch();
}
render({ adminAllTokens }, {}) {
return (
<div>
{adminAllTokens.length > 0 &&
adminAllTokens.map(
user => user && (
<div>{user.name}</div>
)
)
}
</div>
);
}
}
// query used directly in the componentDidMount hook
const AllRefreshTokens = gql`
query {
allUsers {
refreshToken
email
}
}
`;
function mapStateToProps(state, ownProps) {
return {
adminAllTokens: state.auth.adminAllTokens
};
}
function mapDispatchToProps(dispatch) {
return {
receivedAdminTokens: tokens => {
dispatch(adminTokensReceived(tokens));
}
};
}
const reduxWrapper = connect(mapStateToProps, mapDispatchToProps);
// use withApollo enhancer as a HOC
export default compose(reduxWrapper, withApollo)(Admin);
The withApollo()
enhancer provides direct access to the Apollo Client instance, meaning you can perform custom queries from within the component it’s self. A client
prop is passed to the component when it’s added to the compose
function, allowing us to run the GraphQL query in the componentDidMount()
hook, and then push the received data to the redux store in the componentDidMount()
, without the infinite loop.
This setup above works, but doesn’t seem quite right for the following reasons:
- It goes against what Apollo is trying to achieve because there are 2 stores in use.
- It means the full capabilities of Apollo are not used, making the setup feel disjointed.
- It doesn’t promote very good separation between component UI and query logic.
As mentioned earlier, Apollo is able to do the main tasks I outlined; storing queried data in a local store, and managing server request progress to show loading messages, all without Redux. The data returned from a query gets stored in a local cache/store by default, and is accessed by other components hooked up to the graphql
higher order function. Also the server query progress is handled by each component automatically, having a loading
prop which is a boolean based on if the query is currently fetching from the server.
So here’s the Apollo only solution, including the local store/cache (automatically applied) and the loading message indicator:
// routes/admin/index.js
...
class Admin extends Component {
constructor(props) {
super(props);
}
componentDidMount() {}
render({ newModifiedData, loading }, {}) {
let userWrap = loading
? 'loading'
: newModifiedData.length > 0 &&
newModifiedData.map(
user => user && (
<div>{user.name}</div>
)
);
return (
<div>
{userWrap}
</div>
);
}
}
const AllRefreshTokens = gql`
query {
allUsers {
refreshToken
email
}
}
`;
const gqlWrapper = graphql(AllRefreshTokens, {
props: ({ ownProps, data }) => {
let newModifiedData = someFunction();
return {
...data,
newModifiedData
};
}
});
export default compose(gqlWrapper)(Admin);
The above snippet shows the important and defining features of Apollo Client for me. It highlights how Apollo and GraphQL querying is very much declarative as it focuses on the end goal, allowing us to specify exactly what data we need at any time. This is in contrast to Redux which is an imperative approach to retrieving data, as the focus there is on how the data is received and used with the many steps involved in passing data between component, actions, reducers etc.
I actually like Redux because of it’s imperative approach as I feel I get more control over the data. When I was introducing Apollo to a project early on I was trying to use Redux as well for this reason. After a while I began to realise the power of Apollo, and that for pretty much all situations I came across, Apollo had a built in alternative way of handling my problem.
The above few snippets of code have been showing how to do basic queries in a small admin area page in my repo, but here’s one for mutations:
// routes/register/index.js
class Register extends Component {
constructor(props) {
super(props)
this.state = {
email: null,
password: null,
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(e) {
const value = e.target.value
const name = e.target.name
this.setState({
[name]: value,
})
}
handleSubmit(e) {
let datav = this.state
// function passed in from the GraphQL HOC
this.props.registerUser({
emaila: datav.email,
password: datav.password,
})
e.preventDefault()
}
render({ mutation }, {}) {
return <div></div>
}
}
const registerMutation = gql`
mutation($emaila: String!, $password: String!) {
registerUser(email: $emaila, password: $password) { # the mutation name on server
email
}
}
`
const gqlWrapper = graphql(registerMutation, {
props: ({ mutate, ownProps }) => ({
registerUser: (userDetails) => {
const { mutation } = ownProps
mutation.set({
loading: true,
})
mutate({
variables: { ...userDetails },
})
.then((userData) => {
route("/")
mutation.set({
loading: false,
})
})
.catch((err) => {
mutation.set({
loading: false,
error: err,
})
})
},
}),
})
export default compose(mutationState(), gqlWrapper)(Register)
The mutations follow pretty much the same setup, but instead of injecting the component with a load of data about the query, we only receive a mutate function. Again, this is kept separated from the component and used in the props
option of the GraphQL query, as opposed to using this.props.mutate()
within the handleSubmit()
component function.
I won’t go into loads of detail on mutations, but one thing worth noting is that loading
and error
states of the mutation are not provided like they are with queries for reasons decided by the Apollo team. Instead, a few people have developed HOC’s which handle loading states in a re-usable format.
Through all the time I was trying to decide whether to use Redux or not during the process up to now, the loading state for mutations was the only place where I thought it could be useful. It would make sense to dispatch actions to set the state before and after an async request, like one frequently would in with Redux, but I couldn’t justify bringing in and linking up Redux to a component just for mutation states.
Managing local state - the Apollo way
So far we’ve covered how most of Redux is no longer needed alongside Apollo because Apollo can replace Redux in terms of storing data from server queries in a local store, and how Apollo provides functionality to give us progress updates on queries to create loading messages. However, there is one thing which Redux is amazing for, and that’s managing local state which is not related to server activity. For me this has usually been related to UI, or local application specific state that doesn’t involve server calls.
I recently made a web based game/application in React for a client at indigoRiver, based on the popular 80’s UK TV game show, Blockbusters. This was largely built using Redux, and most of the game play happened locally in the browser. This meant constant updates to the local store, and the only server requests were to send the game scores at the end of a game.
Apollo has a lot of built in functionality for managing non-data based local state. I won’t go into loads of detail here because it’s covered really well in the documentation. For a quick overview though - this relies on apollo-link-state
, which is a relatively new feature in Apollo Client. I believe it was introduced when Redux was dropped from being used as the store in Apollo, but I could be wrong.
apollo-link-state
allows you to write local resolvers and query/mutate your local store. This is not to be confused with the direct cache access from the official documentation, which instead focuses on performing updates to your local store straight after a regular query or mutation.
One final thing to note is that local data, queries and mutations can be tracked using the Apollo Client add-on for Chrome. This is a massive help.
Full code can now be found on Github for my preact-jwt-apollo-boilerplate.
I have split the project into Git branches which show each stage of development from the below:
Final thoughts
I’ve covered how Apollo really has an answer for most problems I’d originally questioned. I approached Apollo after using Redux for most of my big projects, so was naturally trying to shoe-horn Redux into an Apollo mindset. Ultimately I don’t think it works as well as I’d have liked, and in the end I discovered that Apollo has an alternative for everything I wanted.
I do love Redux, and I’ll still use it in a lot of my projects - perhaps the smaller ones. I feel Apollo excels when it comes to large scale applications, but I have not built something big enough with it yet to really find that out for myself. There are a lot of people who want Redux back from what I’ve seen on all the Github discussions, but I think people may be warming to the new way now.
Thanks for reading, and Tweet me if I’ve messed up or you have a question about anything :)
Senior Engineer at Haven