How to Build a React Website Powered by the Cosmic JS GraphQL API

May 25, 2017 0 Comments

How to Build a React Website Powered by the Cosmic JS GraphQL API

 

 

This article originally appeared on the Cosmic JS Blog.

Building and maintaining a React app can be no mean feat. There plenty of tutorials out there covering the technical aspects of making a React app, but as with any technology, it’s often hard to find information on best practices.

In this post we’ll be exploring some tips, tricks, and techniques we’ve learnt whilst producing React/GraphQL apps for our clients. Hopefully these will help you make your project more performant and simplify maintenance.

View demo
Install the app on Cosmic JS
Check out the codebase on GitHub

If you’ve been using React for a while, your first instinct when starting a new project might be to set up a state management library like Redux, MobX, or freactal. These are really powerful solutions for state management, that can make wrestling with the state of a large application much more manageable.

But, like any library, you shouldn’t start using these state management solutions until you actually need to! For a simple blog like the one we’ll be building today the only state you actually need is the current URL.

“But!” I hear you cry “How will I store/cache/handle the API-first data I’m fetching over the network, from a great service like Cosmic JS?”. Well worry not! In the second half of this post we’re going to explore GraphQL, a system for declaratively fetching data from a server and specifically the Apollo GraphQL client for simply interfacing with GraphQL.

We’re focusing on a simple, view only app in this post, but it’s worth mentioning that the state provided by class based React components is often sufficient for bits of state that only affect a localised part of your app. Dan Abramov (creator of Redux) has written in more detail about this subject here

Of course, if we don’t have some state, we’d just be displaying our whole web app all at once. Luckily, your browser provides a built in state store with undo history, frictionless sharing, and a simple interface: your URL bar.

The excellent React Router library provides a simple and expressive interface for navigating around your app. Most of the routing in our example app is handled in the following file:

// src/components/posts.js
import styled from "styled-components";
import { Route, Switch, } from "react-router";
import Post, { Blank, Home, FourOhFour, } from "./post";
import Sidebar from "./Sidebar";
const PostsStyled = styled.div<br>    background-color: ${R.path([&quot;theme&quot;, &quot;white&quot;,])};<br>    flex-direction: row;<br>;
export default () => (
<PostsStyled>
        <Route path = "/post" component = { Sidebar } />
        <Switch> 
<Route path = "/post/:postSlug" component = { Post } />
<Route path = "/post/" component = { Blank } />
<Route path = "/" exact component = { Home } />
<Route component = { FourOhFour } />
</Switch>
    </PostsStyled>
);

The first Route renders the sidebar for any URL beginning with /post

The Switch component renders the first of its children with a matching path. Our routing configuration does the following:

  • If the URL is /post/some-post-slug we show the post with the slug some-post-slug
  • If the URL is /post we only show the Sidebar that lets you select a post
  • If the URL is / we show the home page
  • For any other URL we show the 404 page

All this together means we can simply switch between all the different views of our app just by changing the URL.

React Router provides a Link component, that acts like a supercharged <a> tag. You should use Link for any hyperlinks that don’t lead out of your website.

CSS precompilers like SASS first enabled web developers to start using variables and functions in their styles. Then React came along and popularised the inline style system:

<div
style = {{
display: "flex",
backgroundColor: "red",
color: "white",
margin: "4px",
}}
/>

The hottest new trend is Styled Components, which allows you to create new components by specifying a component, and the CSS styles you’d like to apply to it. These styles are automatically vendor-prefixed, and are all converted to a stylesheet in the end.

const Link = styled.a<br>   color: white;<br>   font-size: 0.8em;<br>   text-decoration: none;<br>;

Styled Components also provide a way to set global variables that are inherited by each styled component. The ThemeProvider component can be used to supply variables to each styled component like so:

const theme = {
white: "#fff",
blue: "#00afd7",
};
export default () => (
<ThemeProvider theme = { theme }>
<App/>
</ThemeProvider>
);

Now every styled component that is a child of App can access those variables using a function in the styles:

const Link = styled.a<br>   color: ${ (props) =&gt; props.theme.blue };<br>   font-size: 0.8em;<br>   text-decoration: none;<br>

The theme object can be any javascript object, and the function inside the ${ } block can be any function, so there’s a huge range of cool stuff you can do in your styled components while still keeping all your variables in one unified place.

GraphQL is a declarative, self documenting API specification that allows you to ask your API only for the data you need. It imposes a few restrictions and ideas on your API, that allows a GraphQL Client to create some really cool features, including:

  • Automatic caching.
  • Smart resolving data requests from stored data.
  • Connecting data fetching to the components that display the data.

We’re going to be going through and explaining all the steps we’ve used in our example project, but if you’d like a more complete explanation of the GraphQL protocol, you can read about it in full here.

The GraphQL API provided by Cosmic JS has 3 queries:

  • objects: gets all the objects in a bucket
  • objectsByType: gets all the objects of a certain type in a bucket
  • object: gets a specific object by its slug

And those queries are documented in full here.

First we need to set up our ApolloClient:

//src/GraphQL/index.js
import { ApolloClient, createNetworkInterface, } from "react-apollo";
const networkInterface = createNetworkInterface({
uri: "https://graphql.cosmicjs.com/v1";,
});
const client = new ApolloClient({
networkInterface,
});
export default client;

Which we then provide to the rest of our app using the ApolloProvider:

//src/app.js
import React from "react";
import { ApolloProvider, } from "react-apollo";
import styled, { ThemeProvider, } from "styled-components";
import client from "./GraphQL";
export default () => (
<ThemeProvider theme = { theme }>
<ApolloProvider client = { client }>
<App />
</ApolloProvider>
</ThemeProvider>
);

The Apollo provider means that any component in our app can connect itself to a GraphQL query, meaning each component can ask the client to fetch exactly the data needed to render itself. Don’t worry about multiple components spamming the server with requests; the ApolloClient handles caching and de-duplication itself!

We’ll now spend a little time exploring some methods you can use to make GraphQL a little nicer to use, before we start exploring how we’ve used it in this app. We’re not including all the code necessary to run the example in this blog post, but you can find the source code for our demo project here. Follow along!

Sometimes you want to get the same fields from an object in two different queries, GraphQL provides a system to do this in the form of Fragments. Fragments allow you to pick some fields from an object, and ask for only those fields.

For example, in the sidebar we only want some basic information about a post:

fragment PostPreview on Object {
slug
typeSlug: typeslug
title
modifiedAt: modified
at
}

But in the post itself we want all that information, plus some more:

fragment PostAllContent on Object {
...PostPreview
content
metadata
order
}

We can then use the PostPreview fragment in the query used by our Sidebar:

# getAllPostsQuery
query($bucketSlug: String! $readKey: String!){
objects: objectsByType(bucketslug: $bucketSlug, readkey: $readKey, typeslug: "posts") {
...PostPreview
}
}

And the PostAllContent fragment in the query used by our Post component

# getPostQuery
query($bucketSlug: String! $readKey: String! $postSlug: String!){
object(bucket
slug: $bucketSlug, readkey: $readKey, slug: $postSlug) {
...PostAllContent
}
}

Fragments are great for two reasons:

  1. They allow you to modularise and reuse the properties you want to get from a query
  2. They ensure that two queries which should get the same information always stay in sync, so Apollo can successfully cache the results

You’ll notice that the above queries have 2/3 input fields:

  • $bucketString: The slug of the bucket we’d like to get objects from
  • $readKey: The read key (if needed) to read from the bucket
  • $postSlug: The slug of the specific object we want to get (if needed)

These variables are used to direct the query to the correct data. Apollo gives us a powerful API to set these variables, but often for simple components it’s easier to just set them using props:

For variables that are the same across our app, like $bucketSlug, we can add them to our components using their defaultProps:

//src/components/sidebar.js
const Sidebar = graphql(getAllPostsQuery, { name: "allPosts", })(
props => (
<SidebarStyled>
<Nav>
<SidebarText>
Posts
</SidebarText>
            {
props.allPosts.loading
? <Loading />
: props.allPosts.objects.map(({ slug, ...rest }) => (
<PostLink key = { slug } slug = { slug } { ...rest } />
))
}
         </Nav>
</SidebarStyled>
)
);
SideBar.defaultProps = {
bucketSlug: config.bucket.slug,
readKey: config.bucket["read
key"],
};

For variables that change for different instances of a component, like $postSlug, you can pass them in as a prop to each instance of a component:

//src/components/post.js
const PostWrapper = GraphQL(getPostQuery)(props => (
<PostContainerStyled>
{
props.data.loading
? <Loading />
: <Post
noShare = { props.noShare }
title = { R.path(["data", "object", "title",])(props) }
content = { R.path(["data", "object", "content",])(props) }
/>
}
</PostContainerStyled>
));
PostWrapper.defaultProps = {
bucketSlug: config.bucket.slug,
readKey: config.bucket["readkey"],
};
export const Home = () => <PostWrapper noShare postSlug = "home" />;

It’s always good to prefetch our data before the user needs it, this speeds up page transition time and makes for a nicer UX, Apollo provides a very simple way to do this.

While the Sidebar gets the query it need to display a preview of each post, it also performs another query:

#getAllPostsWithExtraQuery
query($bucketSlug: String! $readKey: String!){
objectsWithExtra: objectsByType(bucket
slug: $bucketSlug, readkey: $readKey, typeslug: "posts") {
...PostAllContent
}
}

This query gets all the fields of every post, meaning all that data is already loaded into the cache before we navigate to a Post page. You can attach multiple queries to a component using the compose function from the react-apollo package.

//src/components/sidebar.js
const Sidebar = compose(
GraphQL(getAllPostsQuery, { name: "allPosts", }),
GraphQL(getAllPostsWithExtraQuery, { name: "allPostsPreFetch", }),
)(props => (
<SidebarStyled>
<Nav>
<SidebarText>
Posts
</SidebarText>
            <Line />
            {props.allPosts.loading
? <Loading />
: props.allPosts.objects.map(({ slug, ...rest }) => (
<PostLink key = { slug } slug = { slug } { ...rest } />
))}
        </Nav>
</SidebarStyled>
));
SideBar.defaultProps = {
bucketSlug: config.bucket.slug,
readKey: config.bucket["readkey"],
};

However, if you were to just do this, you would see no improvement in your network performance, and every time you loaded a new post you’d have to make a new network request. To benefit from this preloading we have to tell the ApolloClient a few more things.

By default, ApolloClient assumes that every Object returned by your API is identifiable by a field called id or _id. In Cosmic JS, each object is identifiable by a field called slug.

Telling Apollo about this is simple:

import { ApolloClient, createNetworkInterface, } from "react-apollo";
import { toIdValue, } from "Apollo-client";
// ------------------------------
const networkInterface = createNetworkInterface({
uri: "https://GraphQL.cosmicjs.com/v1";,
});
const dataIdFromObject = ({ _typename, slug, }) => _typename + slug;
const customResolvers = {
Query: {
object: (
, args) =>
toIdValue(
dataIdFromObject({ _typename: "Object", slug: args.slug, }),
),
},
};
const client = new ApolloClient({
networkInterface,
dataIdFromObject,
customResolvers,
});
//------------------------------
export default client

The function dataIdFromObject tells ApolloClient how to generate a unique ID from any object it gets.

The object customResolvers tells ApolloClient that whenever we make an object query, we can try looking in the cache using the query variable slug.

Now our sidebar preloads all posts using getAllPostsWithExtraQuery, and any future calls to get Post data will be served by ApolloClient’s cache, instead of the network.

Finally, Apollo provides us with another nifty technique to improve the developer experience.

If you’re using webpack as part of your build system you can keep all your GraphQL queries and mutations in seperate files, and import them into javascript like any other file. This only only means to can benefit from compartmentalised code & syntax highlighting, it also means that webpack can pre-compile your GraphQL queries into Apollo’s own internal representation at build time, rather than in your user’s browser.

Integrating the GraphQL loader into webpack is easy, you just have to include the following code in your webpack config:

module: {
rules: [
{
test: /.(GraphQL|gql)$/,
exclude: /node
modules/,
loader: 'GraphQL-tag/loader',
},
],
},

Now you know some of the tips, tricks, and techniques we’ve learnt from using React and GraphQL in production apps. If you’ve learnt something, please share this article!

If you’re making a static site, or anything else, with Cosmic JS get in touch on our Slack or Twitter, we’d love to see what you’re making.

This post was written by Codogo, an award-winning digital agency with a passion for creating amazing digital experiences.


Tag cloud