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:
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:
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:
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
If you'd like to do some further research, the Apollo docs are a great place to start.