Development

React Prototyping with Apollo Client Schemas

Learn how to use Apollo Client to leverage client-side schema for creating a React prototype.

9 min
September 15, 2020
Chris Held
Development Lead

Building a prototype is a great way to validate an idea or to gather feedback from users without taking on the risk of having to build out an entire application. Prototypes are a low cost solution to making your product more successful in the long run. It can save headaches for your team and the customers that use the product.


## Why use Apollo Client for React prototyping?

In this tutorial, we'll take a look at Apollo Client and how we can leverage a client-side schema to create a React prototype. This will set us up for success when we're ready to build out an API to talk to our front end.


Apollo Client is a tool used to manage your client-side data. It's typically paired with Apollo Server, but it will work with any GraphQL server implementation - which makes it great for prototyping. Even if we choose a different GraphQL implementation for our server like Absinthe later, we can still keep our front end queries as long as the schema is defined the way we're expecting.


## What we will create

For our demo, we're going to create an app that will return some information about our user's location based on their IP Address.

Let's get started!

## The walkthrough

I walk through every part of this blog post in this video below. You can watch the video to see it happen in real time and reference the code below to try it out for yourself. You can also read through the sections at your own pace. The choice is yours!


First we'll spin up a react app and install apollo:



   -- CODE line-numbers language-js --

   <!--

     npx create-react-app apollo-client-schema-demo

     cd apollo-client-schema-demo

     npm i

     npm install @apollo/client graphql

   -->




Now, let's create a component to display our user's information. We don't really need to worry about where the data is coming from right now so we'll use static data. Create an `IPInfo.js` file that looks like this:



   -- CODE line-numbers language-js --

   <!--

     import React from "react";


     const IPInfo = () => {

       const data = {

         ipAddress: "1.1.1.1",

         city: {

           name: "Sheboygan",

           population: 123456,

         },

         country: {

           name: "USA",

           population: 123456,

         },

       };


       return (

         <main className="App">

           <h1>Howdy!</h1>

           <p>Your IP Address is {data.ipAddress}</p>

           <p>

             {`Your city, ${data.city.name}, has a current population of

             ${data.city.population}`}

           </p>

           <p>

             {`Your Country, ${data.country.name}, has a current population of

             ${data.country.population}`}

           </p>

           <p>Cool, huh?</p>

         </main>

       );

     };


     export default IPInfo;

   -->




Let's also edit our `App.js` file to show this component:



   -- CODE line-numbers language-js --

   <!--

     [...]

     function App() {

       return (

           <div className="container">

             <IPInfo />

           </div>

       );

     }

     [...]

   -->




Then let's edit our `App.css` file slightly to clean it up:



   -- CODE line-numbers language-css --

   <!--

     body {

       margin: 2rem;

     }


     .container {

       max-width: 800px;

       margin: auto;

     }

   -->




If we run `npm start`, we should be greeted with something like this:


react app prototype example progress 01


Now we need to set up an Apollo client. Add the following to `App.js`:



   -- CODE line-numbers language-js --

   <!--

     import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";


     const client = new ApolloClient({

       uri: "https://48p1r2roz4.sse.codesandbox.io",

       cache: new InMemoryCache(),

     });

   -->




This sets up an instance of ApolloClient. The URL we chose is from the Apollo Documentation and can be used as a placeholder until we have a real server to point to. The contents of the server don't really matter since we'll only be pointing at our client schema, but it is a required field when instantiating a client.


In order to tie our app to Apollo, we need to wrap it in an instance of `ApolloProvider`. To do that,  we'll need to edit our App component:



   -- CODE line-numbers language-js --

   <!--

     function App() {

       return (

         <ApolloProvider client={client}>

           <div className="container">

             <IPInfo />

           </div>

         </ApolloProvider>

       );

     }

   -->




If we refresh, we shouldn't see any difference since we aren't actually querying for anything. To do that without having an actual server to call, we can define `typeDefs` in our app and pass them in to our client instantiation. Let's make some modifications to `App.js`:



   -- CODE line-numbers language-js --

   <!--

     import React from "react";

     import "./App.css";


     import {

       ApolloClient,

       ApolloProvider,

       InMemoryCache,

       gql,

     } from "@apollo/client";

     import IPInfo from "./IPInfo";


     const typeDefs = gql`

       extend type Query {

         client: Client!

       }


       type Client {

         ipAddress: IPAddress!

       }


       type IPAddress {

         address: String!

         city: City

         country: Country

       }


       type City {

         name: String!

         population: Int

       }


       type Country {

         name: String!

         population: Int!

       }

     `;


     const client = new ApolloClient({

       uri: "https://48p1r2roz4.sse.codesandbox.io",

       cache: new InMemoryCache(),

       typeDefs,

     });


     function App() {

       return (

         <ApolloProvider client={client}>

           <div className="container">

             <IPInfo />

           </div>

         </ApolloProvider>

       );

     }


     export default App;

   -->



Here, we're defining `typeDefs` and creating a `client` query and some types to support it, then passing that into our `client` constructor. Now we can use apollo's `useQuery` hook to fetch the results of that query, even though we still haven't written anything to resolve it or built out a server. Let's do that in `IPInfo.js`:



   -- CODE line-numbers language-js --

   <!--

     import React from "react";

     import { useQuery, gql } from "@apollo/client";


     const CLIENT_QUERY = gql`

       {

         client @client {

           ipAddress {

             address

             city {

               name

               population

             }

             country {

               name

               population

             }

           }

         }

       }

     `;


     const IPInfo = () => {

       const {

         data: {

           client: { ipAddress: { address, city = {}, country = {} } = {} } = {},

         } = {},

         loading,

         error,

       } = useQuery(CLIENT_QUERY);


       if (loading) {

         return (

           <p>

             Hmm...{" "}

             <span role="img" aria-label="thinking emoji">

               🤔

             </span>

           </p>

         );

       }


       if (error) {

         return (

           <p>

             Ruh Roh{" "}

             <span role="img" aria-label="sad emoji">

               😫

             </span>

           </p>

         );

       }


       return (

         <main className="App">

           <h1>Howdy!</h1>

           <p>Your IP Address is {address}</p>

           <p>

             {`Your city, ${city.name}, has a current population of

             ${city.population}`}

           </p>

           <p>

             {`Your Country, ${country.name}, has a current population of

             ${country.population}`}

           </p>

           <p>Cool, huh?</p>

         </main>

       );

     };


     export default IPInfo;

   -->





We've changed a lot here, so let's step through.


First we define our graphql query. Nothing very special there if you're familiar with graphql, but note the `@client` directive. That tells apollo that this doesn't exist on the server, so there's no need to ask the server for this.


In the actual component code, we take advantage of apollo's `useQuery` hook to make our query:



   -- CODE line-numbers language-js --

   <!--

     const {

       data: {

         client: { ipAddress: { address, city = {}, country = {} } = {} } = {},

       } = {},

       loading,

       error,

     } = useQuery(CLIENT_QUERY);

   -->




This gives us all the data we need to power our form, plus a few variables to manage different query states. Our markup has remained largely the same, though we did add a bit to handle loading and error states.


If we refresh our page, we'll see a whole lot of nothing:


react app prototype example progress 02


Why is that? Because in our client schema we only defined the shape of our data - but not it's contents.


To do that, we need to create a resolver. Let's add one right underneath our schema in App.js:



   -- CODE line-numbers language-js --

   <!--

     const resolvers = {

       Query: {

         client: () => ({

           ipAddress: {

             address: "172.220.20.36",

             city: {

               name: "Sheboygan",

               population: 48895,

             },

             country: {

               name: "United States of America",

               population: 325145963,

             },

           },

         }),

       },

     };


     const client = new ApolloClient({

       uri: "https://48p1r2roz4.sse.codesandbox.io",

       cache: new InMemoryCache(),

       typeDefs,

       resolvers,

     });

   -->




Don't forget to add your `resolvers` object to your client.


In our resolver, we defined what should be returned when something calls the `client` query. We could make this more random if we wanted, but this will suit our prototype just fine.

Now if we refresh, we see the data from our resolver:


react app prototype example progress 03


Let's say in parallel we did some research and found out there was a site, everbase.co, that had a schema that perfectly matched our client query. What a coincidence! All we have to do now is update our client uri, remove the `@client` directive from our query, and voila - we have an app connected to real data.


By doing the work of setting up our client and mocking our queries out upfront, we end up laying a lot of infrastructures necessary to complete our application when the time comes.


## More resources for Apollo


See our demo in action

Check out the source


If you'd like to do some further research, the Apollo docs are a great place to start.

Actionable UX audit kit

  • Guide with Checklist
  • UX Audit Template for Figma
  • UX Audit Report Template for Figma
  • Walkthrough Video
By filling out this form you agree to receive our super helpful design newsletter and announcements from the Headway design crew.

Create better products in just 10 minutes per week

Learn how to launch and grow products with less chaos.

See what our crew shares inside our private slack channels to stay on top of industry trends.

By filling out this form you agree to receive a super helpful weekly newsletter and announcements from the Headway crew.