How to Build a ReactJS SPA using Apollo Client, NextJS, Storybook and Material-UI
- 83404 views
- 18 min
- Nov 01, 2018
Modern times demand modern solutions. That’s why as a developer you need to know how to build one the most in-demand types of projects nowadays ‒ a single-page web app, or SPA. Their popularity among product owners can be explained by the fact that such apps don’t load every new page but only pull data from a server. This approach leads to shorter response times and a better customer experience. Another great plus of SPAs is their effectiveness, as they allow developers to reuse code within the app.
React as a library to build single-page applications
For today’s tutorial (and for our own projects), we use ReactJS to build top-notch single-page applications. This choice is based on React’s long list of advantages. Here are the most significant of them.
- Reusable components. React is a component library. By using the same components multiple times, you save time and create easy-to-maintain, bug-free code.
- Virtual DOM. A browser’s DOM is slow. Using a virtual DOM, when you make changes in your data the real DOM tree is compared to the virtual DOM, and then it tracks the differences and only redraws changed elements.
- Open source library. ReactJS is a free open source tool, which means it’s developed not only by Facebook programmers but also with the help of thousands of enthusiasts. Many of them have made significant contributions to advance the library, helping to put it in seventh place in the trending list on GitHub.
- Server-side rendering. Server-side rendering can help you avoid a lot of issues with browser compatibility and improves web indexing.
- Easy to test. React is well-designed for testing and debugging. Using one of numerous external libraries (Jest, in our case), you can get all the functionality you need out of the box.
- Improved readability/maintainability. The React library is based on a component hierarchy that can be reused numerous times, making code readable and improving the maintainability of a web app.
- Performance. React JS has a bunch of features and peculiarities which make it super productive and efficient, such as the virtual DOM, inline functions, and a component structure.
- Developer tool sets. The React tool set lets developers inspect hierarchies of React components in the virtual DOM. These tool sets allow you to choose separate components and check their properties and states. They also help to improve the debugging process.
- Maintained by Facebook. React gets constant support from Facebook developers, who improve it all the time.
- One-way data flow. By making React JS just a view system, developers have managed to provide the ability to change child structures without affecting their parents. This makes code more stable.
- Redux compatibility. While applying React to your project, it’s very easy to use Redux as they’re compatible with each other. This compatibility makes a developer’s life easier.
Building a single-page application with React
As an example for our tutorial, we decided to create a GitHub client based on React JS using the GitHub GraphQL API for interactions. The main features of this client will be search for top repos and new repos.
Below, you can see a list of technologies that we’ve chosen for the project and the reasons why we prefer them.
ReactJS — A declarative and component-based library for creating interactive UIs, React is able to update and render components when data changes.
GraphQL — This query language for working with APIs allows us to get a comprehensive description of data in an API and makes it easier to get exactly the data we need in our requests.
NextJS 6 — This lightweight framework for server-rendered ReactJS applications focuses on pre-rendering to improve performance.
ESLint — We use this tool to check code quality. Apart from checking general style, ESLint helps us to find certain classes of bugs, such as those related to variable scope.
Jest + Enzyme — This combination of instruments makes the testing process much more pleasant. Jest is very fast, can run Snapshot testing, has really helpful fail messages, re-runs only tests that are relevant to changes that were made, and is simple to configure. Enzyme is a useful testing utility for React.
Yarn — An ultra-fast package manager, we like Yarn for its speed, security, and reliability.
Storybook — A development environment for UI components, Storybook allows us to browse component libraries, view the different states of each component, and interactively develop and test components.
Material-UI — These are the React components that help us implement Google Material Design.
Atomic design — This is a methodology for creating design component systems instead of creating pages.
To create a single-page GitHub client based on React JS using the GitHub GraphQL API for interactions, follow the steps below.
Step 1 — Install Yarn
There are two ways to install Yarn. The first option is to use npm:
Another option is to go to the official download page, get the installer for your operating system, and run it.
Step 2 — Initialize the project
To start, you need to create a simple project by running the following commands:
Then open package.json in the github-client directory and add the following script:
Now everything is ready. To start the dev server, run the following command:
When you run localhost:3000 in your browser, you’ll see the error page
Step 3 — Install Babel
To install the Babel compiler core, use this command:
Babel is a compiler that accepts source code and renders output code. Like many other compilers, it runs in three stages: parse, transform, and print.
But out of the box, Babel doesn’t do anything useful. It basically acts like const babel = code => code, parsing the code and then generating the same code back out again. You’ll need to add plugins for Babel to do anything useful.
3.1 Install the Babel module resolver
When using Babel, you can add new root directories that contain modules and set up custom aliases for directories, specific files, or even other npm modules.
To install the Babel module resolver, use this command:
After that, update .babelrc config to import dependencies without declaring a related path.
This allows you to write this
Instead of this:
Step 4 — Configure linters
To avoid lots of refactoring in future, integrate linters in your app. To do that, add eslint as a development dependency:
You need a wrapper for Babel’s parser used for eslint.
Use Yarn to install this package:
Babel-eslint allows you to lint all valid Babel code.
There are also a few dependencies that you’ll have to install.
This package provides Airbnb’s .eslintrc as an extensible shared config.
Install the ESLint plugin with rules that help validate proper imports:
Install Static AST checker for accessibility rules on JSX elements:
Install the eslint plugin for React:
Initialize eslint config:
We’re going to use the Airbnb eslint config, so choose the following settings:
How would you like to configure ESLint? — Use a popular style guide
Which style guide do you want to follow? — Airbnb
Do you use React? — Yes
What format do you want your config file to be in? — JSON
Would you like to install them now with npm? — No
Now you should have an .eslintrc.json file with the following configuration:
Step 5 — Integrate Material-UI
In plain English, Material-UI is an open source project that features React components which implement Google’s Material Design.
There are a few additional steps that we need to take before we can start using Material-UI with the NextJS 6 framework.
First of all, we need to install some additional packages.
5.1 Install JSS
5.2 Install react-jss
We use react-jss because it provides components for JSS as an abstraction layer and has the following benefits compared to a lower-level core:
- Theming support
- Critical CSS extraction
- Lazy evaluation — a sheet is created only when a component is mounted
- Auto attach/detach — a sheet is rendered in the DOM when a component is about to mount, and is removed when no element needs it
- Style sheets get shared between all elements
- Function values and rules are updated automatically with props
5.3 Install the styled-jsx package
Styled-jsx is a full, scoped, and component-friendly CSS support for JSX that’s rendered on the server or the client.
5.4 Install Material-UI core and icon packages
@material-ui/core is a set of React components that implement Google Material Design.
@materail-ui/icons is a set of components with SVG icons.
If you want to use Material-UI in the Next.js framework, you need to configure JSS injection to render material styles properly (with server-side rendering) as shown in the official material-ui repo.
5.5 Now create getPageContext.js
Next.js uses the App component to initialize pages. But you can override it and control page initialization, allowing you to do amazing things like:
- persist the layout between page changes;
- maintain state when navigating pages;
- implement custom error handling using the DidCatch component;
- inject additional data into pages (for example, by processing GraphQL queries).
To override the App component, create a ./pages/_app.js file and override the App class as shown below:
Pages in Next.js skip the definition of the surrounding document’s markup. For example, you never include <html>, <body>, and so on. To override that default behavior, create a file at ./pages/_document.js where you can extend the Document class.
You need to use the codebase from the official material-ui repo example with Next.js:
Now we’re ready to implement some pages with components.
Step 6 — Storybook integration
Let’s integrate Storybook, a handy UI development environment. Here you can find a start guide for React.
First of all, you need to add @storybook/react to your project. To do that, simply run:
Then add the following script to your JSON package in order to start the Storybook later in this guide:
6.1 Create a config file.
A Storybook can be configured in several different ways. That’s why we need a config directory. We’ve added a -c option to the above script, mentioning .storybook as the config directory.
For the basic Storybook configuration file, simply tell Storybook where to find stories.
For that, create a file at .storybook/config.js with the following content:
Here we use Webpack’s require.context to load modules dynamically.
6.2 Add Babel plugin
This replicates Webpack require.context.
6.3 Update the Babel config
6.4 Initialize the require context hook in testConfig
All files with a .stories extension inside src/components will be required dynamically.
Create a separate Babel config for Storybook to avoid conflicts with different environments.
Adjust eslint config (so we don’t see warnings when importing Storybook packages):
Storybook is all about writing stories. Usually, a story contains a single state of one of your components, very similar to a visual test case.
In a nutshell, a story is a function that returns a React element.
Step 7 — Atomic design
Atomic design helps us build consistent, solid, and reusable design systems. More than that, in the world of React that stimulates componentization, atomic design is used unconsciously. But don’t forget that It can become a powerful tool only if you use it in the right way.
First of all, create an index.js entry point for the components directory:
Atomic design should be a solution, not another problem. If you want to create a component and don’t know where to put it (atoms, molecules, organisms, etc.), don’t worry; don’t think too much, just put it anywhere. After you realize what it is, just move the component folder to the right place. Everything else should work.
This is possible because all components are dynamically exported to components/index.js and imported so that the atomic design structure doesn’t matter:
Let’s create our first atom — a material button.
7.1 Create atoms
Atoms are the basic building blocks of matter. Applied to web interfaces, atoms are HTML tags, such as form labels, inputs, and buttons. They can also include more abstract elements like color palettes and fonts as well as more invisible aspects of an interface like animations.
Create a button atom
After that, write a story for this atom:
Now let’s run it:
We’re going to create a few more atoms using the same approach:
7.2 Create molecules
Molecules are groups of atoms that are bonded together. They take on their own properties and serve as the backbone of our design systems.
Сreate SimpleCard, the first molecule component
After that, write a story for this molecule:
We’re going to create a few more molecules using the same approach:
7.3 Build organisms
Organisms are groups of molecules joined together to form a relatively complex, distinct section of an interface.
Building up from molecules to organisms encourages creating standalone, portable, reusable components.
Create a header with a swipeable menu organism
After that, write a story for this organism:
7.4 Create templates
Templates are page-level objects that place components into a layout and articulate the design’s underlying content structure.
Build the home template:
After that, write a story for this template
Step 8 — Create pages
Now we can use Next.js pages as an entry point.
The file system is the main API. Every .js file becomes a route that gets automatically processed and rendered. If we run yarn dev, we can access this page at localhost:3000.
Step 9 — Implementing authentication
To communicate with the GitHub GraphQL API, we need to create a GitHub application first. Follow this guide to create your GitHub app. This guide will help you to develop environment Homepage URL, and this one is necessary for the authorization callback URL.
To use GitHub secret keys, you’ll need to use the dotenv package.
Сreate next.config.js with following commands:
Now you need to add the GitHub app secret keys. We can use the .env file for storing secret API keys that will be available only on the server side.
Add this file to .gitignore.
9.1 Create containers
If we need to implement some component with its own state management or side effects (in other words, a smart component), we can place it in the containers folder. All components with GraphQL/REST requests will be there.
We can use GraphQL queries in our containers using the react-apollo query component.
9.2 GitHub login button container
We need a login button container to make a request to the GitHub authentication endpoint and redirect to the callback page.
9.3 Callback page
GitHub redirects to a callback URL on your website (which you provide when registering your app with GitHub).
We need to implement the callback page that will be used to obtain the access_token.
Install isomorphic-unfetch (Tiny 500b fetch "barely-polyfill"). We will use this package for authenticating REST requests only.
Now you need to implement callback page:
Data returned from getInitialProps is serialized during server rendering, similar to JSON.stringify. Make sure the returned object from getInitialProps is a plain object and doesn’t use Date, Map, or Set.
For the initial page load, getInitialProps will execute on the server only. getInitialProps will only be executed on the client when navigating to a different route via the Link component or using the routing APIs.
Step 10 — Integrate GraphQL with Apollo
The easiest way to get started with Apollo Client is by using Apollo Boost. It’s a starter kit that configures your client with recommended settings.
Install the following packages:
- apollo-boost — A package containing everything you need to set up the Apollo Client
- react-apollo — A view layer integration for React
- graphql — Also parses your GraphQL queries
- graphql-tag — Provides a template literal tag that you can use to concisely write a GraphQL query that’s parsed into the standard GraphQL AST
Now that you have all the dependencies you need, let’s create your Apollo Client. The only thing you need to get started is the endpoint for your GraphQL server.
If you don’t pass the URI directly, it defaults to the /graphql endpoint on the same host your app is served from.
10.1 GraphQL authentication
Apollo Client uses a very flexible Apollo Link that includes several options for authentication.
It’s very easy to tell your network interface to send a cookie along with every request. You just need to pass the headers option, for instance headers: 'token'.
In this example, we’ll pull the login token from cookies every time a request is sent:
After that, we can make requests to the GitHub API using our token from cookies.
10.2 GraphQL queries
Let’s implement our first GraphQL query.
This query finds the last 50 repositories with more than 10,000 stars.
Now let’s implement SearchRepoList container
Now let’s implement the Next.js page:
Step 11 — Testing
Testing is one of the most important stages of development. It’s time to test everything that we’ve implemented so far.
11.1 Add Jest for testing
You need to add Jest as a development dependency:
11.2 Add enzyme
We need different Babel presets for the test environment, specifically for the Next.js app.
Enzyme expects an adapter to be configured:
11.3 Connect test config
Add to eslintrc.json.
This will add all Jest-related things to your environment, eliminating the linter errors/warnings.
Add to `package.json`:
This allows us to use yarn test to run all Jest specs.
We’ll add a simple test for our button component:
Now if we run yarn test, we should have one passed spec.
After that, we’ll add more simple components (atoms) which we’ll use for the home page.
11.4 Test card atom
11.5 Test SimpleCard molecules
11.6 Test HeaderWithSwipeableMenu organism
11.7 Test the home template
11.8 Create snapshot testing
Snapshot testing is a very useful tool whenever you want to make sure your UI doesn’t change unexpectedly.
To add react-test-render, write this command:
Now let’s test callback page
To get more tutorials on this topic, subscribe to our blog. And feel free to start a discussion in the comments below!