Build a GraphQL + React app with TypeScript
Tags: graphql, react, typescript
Published: 2019-07-08 20:16:26

---
title: Build a GraphQL + React app with TypeScript
published: true
tags: graphql, react, typescript
canonical_url: https://blog.logrocket.com/build-a-graphql-react-app-with-typescript/
---

![](https://thepracticaldev.s3.amazonaws.com/i/uo231wqzwcap7sghv812.png)

GraphQL and TypeScript have both [exploded in adoption](https://2018.stateofjs.com/javascript-flavors/overview/), and when the two are combined with React, they create the ideal developer experience.

GraphQL has transformed the way we think about APIs and utilizes an intuitive key/value pair matching where the client can request the exact data needed to display on a web page or in a mobile app screen. TypeScript extends JavaScript by adding static typing to variables, resulting in fewer bugs and more readable code.

This article walks you through building a client-side application with React and Apollo using the public SpaceX GraphQL API to display information about launches. We will automatically generate TypeScript types for our queries and execute these queries using React Hooks.

The article will assume you have some familiarity with React, GraphQL, and TypeScript, and will focus on integrating them to build a functioning application. If you need additional practice with them, you can find plenty of quality [coding tutorials](https://gitconnected.com/learn) to get you up to speed.

If you get stuck at any point, you can refer to the [source code](https://github.com/treyhuffine/graphql-react-typescript-spacex) or see the [live app](https://spacex-graphql.netlify.com/).

![SpaceX Launch App Gif](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/spacex-launch-app.gif?resize=480%2C262&ssl=1)

## Why GraphQL + TypeScript?

A GraphQL API is required to be strongly typed, and the data is served from a single endpoint. By calling a GET request on this endpoint, the client can receive a fully self-documented representation of the backend, with all available data and the corresponding types.

With the [GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator), we scan our web app directory for query files and match them with the information provided by the GraphQL API to create TypeScript types for all request data. By using GraphQL, we get the props of our React components typed automatically and for free. This leads to fewer bugs and a much faster iteration speed on your products.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Getting started

We will use create-react-app with the TypeScript setting to bootstrap our application. Initialize your app by executing the following command:

```jsx
npx create-react-app graphql-typescript-react --typescript
// NOTE - you will need Node v8.10.0+ and NPM v5.2+
```

By using the `--typescript` flag, CRA will generate your files and `.ts` and `.tsx`, and it will create a `tsconfig.json` file.

Navigate into the app directory:

```jsx
cd graphql-typescript-react
```

Now we can install our additional dependencies. Our app will use Apollo to execute GraphQL API requests. The libraries needed for Apollo are `apollo-boost`, `react-apollo`, `react-apollo-hooks`,  `graphql-tag`, and `graphql`.

`apollo-boost` contains the tools needed to query the API and cache data locally in memory; `react-apollo` provides bindings for React; `react-apollo-hooks` wraps Apollo queries in a React Hook; `graphql-tag` is used to build our query documents; and `graphql` is a peer dependency that provides details of the GraphQL implementation.

```jsx
yarn add apollo-boost react-apollo react-apollo-hooks graphql-tag graphql
```

`graphql-code-generator` is used to automate our TypeScript workflow. We will install the codegen CLI to generate the configuration and plugins we need.

```jsx
yarn add -D @graphql-codegen/cli
```

Set up the codegen configuration be executing the following command:

```jsx
$(npm bin)/graphql-codegen init
```

This will launch a CLI wizard. Do the following steps:

1. Application built with React.
2. The schema is located at [`https://spacexdata.herokuapp.com/graphql`](https://spacexdata.herokuapp.com/graphql).
3. Set your operations and fragments location to `./src/components/**/*.{ts,tsx}` so that it will search all our TypeScript files for query declarations.
4. Use the default plugins “TypeScript”, “TypeScript Operations”, “TypeScript React Apollo.”
5. Update the generated destination to `src/generated/graphql.tsx` (.tsx is required by the react-apollo plugin).
6. Do not generate an introspection file.
7. Use the default `codegen.yml` file.
8. Make your run script `codegen`.

Now install the plugins the CLI tool added to your `package.json` by running the `yarn` command in your CLI.

We will also make one update to our `codegen.yml` file so that it will also generate typed React Hook queries by adding the `withHooks: true` configuration option. Your configuration file should look like the following:

```jsx
overwrite: true
schema: 'https://spacexdata.herokuapp.com/graphql'
documents: './src/components/**/*.ts'
generates:
  src/generated/graphql.tsx:
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-react-apollo'
    config:
      withHooks: true
```

## Writing GraphQL queries and generating types

A primary benefit of GraphQL is that it utilizes declarative data fetching. We are able to write queries that live alongside the components that use them, and the UI is able to request exactly what it needs to render.

When working with REST APIs, we would need to find documentation that may or may not be up to date. If there were ever any problems with REST, we would need to make requests against the API and console.log the results to debug the data.

GraphQL solves this problem by allowing you to visit the URL and see a fully defined schema and execute requests against it, all in the UI. Visit [https://spacexdata.herokuapp.com/graphql](https://spacexdata.herokuapp.com/graphql) to see exactly what data you will be working with.

![SpaceX Launch Data](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/spacex-launch-data.png?resize=1600%2C807&ssl=1)

While we have a large amount of SpaceX data available to us, we will display just the information about launches. We’ll have two primary components:

1. A list of `launches` a user can click to learn more about them.
2. A detailed profile of a single `launch`.

For our first component, we’ll query against the `launches` key and request the `flight_number`, `mission_name`, and `launch_year`. We’ll display this data in a list, and when a user clicks one of the items, we’ll query against the `launch` key for a larger set of data for that rocket. Let’s test our first query in GraphQL playground.

![First Launch Query In GraphQL Playground](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/first-launch-query-in-graphql-playground.png?resize=1600%2C480&ssl=1)

To write our queries, we first create a `src/components` folder and then create a `src/components/LaunchList` folder. Inside this folder, create `index.tsx`, `LaunchList.tsx`, `query.ts`, and `styles.css` files. Inside the `query.ts` file, we can transfer the query from the playground and place it inside a `gql` string.

```jsx
import gql from 'graphql-tag';

export const QUERY_LAUNCH_LIST = gql`
  query LaunchList {
    launches {
      flight_number
      mission_name
      launch_year
    }
  }
`;
```

Our other query will get more detailed data for a single launch based on the `flight_number`. Since this will be dynamically generated by user interaction, we will need to use [GraphQL variables](https://graphql.org/learn/queries/#variables). We can also test queries with variables in the playground.

Next to the query name, you specify the variable, prepended with a `$` and its type. Then, inside the body, you can use the variable. For our query, we set the `id` of the launch by passing it the `$id` variable, which will be of type `String!`.

![Passing ID As Query Variable](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/passing-id-as-query-var.png?resize=1600%2C703&ssl=1)

We pass in the `id` as a variable, which corresponds to the `flight_number` from the `LaunchList` query. The `LaunchProfile` query will also contain nested objects/types, where we can get values by specifying the keys inside brackets.

For example, the launch contains a `rocket` definition (type `LaunchRocket`), which we will ask for the `rocket_name` and `rocket_type`. To get a better understanding of the fields available for `LaunchRocket`, you are able to use the schema navigator on the side to understand the available data.

Now let’s transfer this query to our application. Create a `src/components/LaunchProfile` folder with `index.tsx`, `LaunchProfile.tsx`, `query.ts`, and `styles.css` files. Inside the `query.ts` file, we paste our query from the playground.

```jsx
import gql from 'graphql-tag';

export const QUERY_LAUNCH_PROFILE = gql`
  query LaunchProfile($id: String!) {
    launch(id: $id) {
      flight_number
      mission_name
      launch_year
      launch_success
      details
      launch_site {
        site_name
      }
      rocket {
        rocket_name
        rocket_type
      }
      links {
        flickr_images
      }
    }
  }
`;
```

Now that we have defined our queries, you are finally able to generate your TypeScript interfaces and the typed hooks. In your terminal, execute:

```jsx
yarn codegen
```

Inside `src/generated/graphql.ts`, you will find all the types needed to define your application, along with the corresponding queries to fetch the GraphQL endpoint to retrieve that data. This file tends to be large, but it is filled with valuable information. I recommend taking the time to skim through it and understand all the types our codegen created based entirely on the GraphQL schema.

For example, inspect `type Launch`, which is the TypeScript representation of the `Launch` object from GraphQL that we were interacting with in the playground. Also scroll to the bottom of the file to see the code generated specifically for the queries that we will be executing — it has created components, HOCs, typed props/queries, and typed hooks.

## Initialize Apollo client

In our `src/index.tsx`, we need to initialize the Apollo client and use the `ApolloProvider` component to add our `client` to React’s context. We also need the `ApolloProviderHooks` component to enable context in the hooks. We initialize a `new ApolloClient` and give it the URI of our GraphQL API, and then we wrap our `<App /> component in the context providers. Your index file should look like the following:`

```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-hooks';
import './index.css';
import App from './App';

const client = new ApolloClient({
  uri: 'https://spacexdata.herokuapp.com/graphql',
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <ApolloHooksProvider client={client}>
      <App />
    </ApolloHooksProvider>
  </ApolloProvider>,
  document.getElementById('root'),
);
```

## Build our components

We now have everything in place we need to execute GraphQL queries through Apollo. Inside the `src/components/LaunchList/index.tsx`, we will create a function component that uses the generated `useLaunchListQuery` hook. The query hooks return `data`, `loading`, and `error` values. We will check for `loading` and an `error` in the container component and pass the `data` to our presentational component.

We will keep the separation of concerns by using this component as a container/smart component, and we will pass the data to a presentation/dumb component that simply displays what it has been given. We will also show basic loading and error states while we wait for the data. Your container component should look like the following:

```jsx
import * as React from 'react';
import { useLaunchListQuery } from '../../generated/graphql';
import LaunchList from './LaunchList';

const LaunchListContainer = () => {
  const { data, error, loading } = useLaunchListQuery();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error || !data) {
    return <div>ERROR</div>;
  }

  return <LaunchList data={data} />;
};

export default LaunchListContainer;
```

Our presentational component will use our typed `data` object to build the UI. We create an ordered list with `<ol>`, and we map through our launches to display the `mission_name` and `launch_year`. 

Our `src/components/LaunchList/LaunchList.tsx` will look like the following:

```jsx
import * as React from 'react';
import { LaunchListQuery } from '../../generated/graphql';
import './styles.css';

interface Props {
  data: LaunchListQuery;
}

const className = 'LaunchList';

const LaunchList: React.FC<Props> = ({ data }) => (
  <div className={className}>
    <h3>Launches</h3>
    <ol className={`${className}__list`}>
      {!!data.launches &&
        data.launches.map(
          (launch, i) =>
            !!launch && (
              <li key={i} className={`${className}__item`}>
                {launch.mission_name} ({launch.launch_year})
              </li>
            ),
        )}
    </ol>
  </div>
);

export default LaunchList;
```

If you are using VS Code, the IntelliSense will show you exactly the values available and provide an autocomplete list since we are using TypeScript. It will also warn us if the data we are using can be `null` or `undefined`.

![Autocompleted List Of Values In VS Code](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/intellisense-vs-code-typescript.png?resize=1600%2C579&ssl=1)

Seriously, how amazing is that? Our editor will code for us. Also, if you need the definition of a type or function, you can `Cmd + t`, hover it with the mouse, and it will give you all the details.

We will also add some CSS styling, which will display our items and allow them to scroll when the list overflows. Inside `src/components/LaunchList/styles.css`, add the following code:

```jsx
.LaunchList {
  height: 100vh;
  overflow: hidden auto;
  background-color: #ececec;
  width: 300px;
  padding-left: 20px;
  padding-right: 20px;
}

.LaunchList__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.LaunchList__item {
  padding-top: 20px;
  padding-bottom: 20px;
  border-top: 1px solid #919191;
  cursor: pointer;
}
```

Now we’ll build our profile component to display more details about the launch. The `index.tsx` file for this component is mostly the same, except we’re using the `Profile` query and components. We also pass a variable to our React hook for the `id` of the launch. For now, we’ll hardcode it to `”42”` and then add dynamic functionality once we have our app laid out.

Inside `src/components/LaunchProfile/index.tsx`, add the following code:

```jsx
import * as React from 'react';
import { useLaunchProfileQuery } from '../../generated/graphql';
import LaunchProfile from './LaunchProfile';

const LaunchProfileContainer = () => {
  const { data, error, loading } = useLaunchProfileQuery({ variables: { id: '42' } });

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>ERROR</div>;
  }

  if (!data) {
    return <div>Select a flight from the panel</div>;
  }

  return <LaunchProfile data={data} />;
};

export default LaunchProfileContainer;
```

We now need to create our presentation component. It will display the launch name and details at the top of the UI and then have a grid of launch images below the description.

The `src/components/LaunchProfile/LaunchProfile.tsx` component will look like the following:

```jsx
import * as React from 'react';
import { LaunchProfileQuery } from '../../generated/graphql';
import './styles.css';

interface Props {
  data: LaunchProfileQuery;
}

const className = 'LaunchProfile';

const LaunchProfile: React.FC<Props> = ({ data }) => {
  if (!data.launch) {
    return <div>No launch available</div>;
  }

  return (
    <div className={className}>
      <div className={`${className}__status`}>
        <span>Flight {data.launch.flight_number}: </span>
        {data.launch.launch_success ? (
          <span className={`${className}__success`}>Success</span>
        ) : (
          <span className={`${className}__failed`}>Failed</span>
        )}
      </div>
      <h1 className={`${className}__title`}>
        {data.launch.mission_name}
        {data.launch.rocket &&
          ` (${data.launch.rocket.rocket_name} | ${data.launch.rocket.rocket_type})`}
      </h1>
      <p className={`${className}__description`}>{data.launch.details}</p>
      {!!data.launch.links && !!data.launch.links.flickr_images && (
        <div className={`${className}__image-list`}>
          {data.launch.links.flickr_images.map(image =>
            image ? <img src={image} className={`${className}__image`} key={image} /> : null,
          )}
        </div>
      )}
    </div>
  );
};

export default LaunchProfile;
```

The final step is to style up this component with CSS. Add the following to your `src/components/LaunchProfile/styles.css` file:

```jsx
.LaunchProfile {
  height: 100vh;
  max-height: 100%;
  width: calc(100vw - 300px);
  overflow: hidden auto;
  padding-left: 20px;
  padding-right: 20px;
}

.LaunchProfile__status {
  margin-top: 40px;
}

.LaunchProfile__title {
  margin-top: 0;
  margin-bottom: 4px;
}

.LaunchProfile__success {
  color: #2cb84b;
}

.LaunchProfile__failed {
  color: #ff695e;
}

.LaunchProfile__image-list {
  display: grid;
  grid-gap: 20px;
  grid-template-columns: repeat(2, 1fr);
  margin-top: 40px;
  padding-bottom: 100px;
}

.LaunchProfile__image {
  width: 100%;
}
```

Now that we have completed a static version of our components, we can view them in the UI. We’ll include our components in the `src/App.tsx` file and also convert `<App />` to a function component. We use a function component to make it simpler and allow us to use hooks when we add the click functionality.

```jsx
import React from 'react';
import LaunchList from './components/LaunchList';
import LaunchProfile from './components/LaunchProfile';

import './App.css';

const App = () => {
  return (
    <div className="App">
      <LaunchList />
      <LaunchProfile />
    </div>
  );
};

export default App;
```

To get the styling we want, we will change `src/App.css` to the following:

```jsx
.App {
  display: flex;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
```

Execute `yarn start` in your terminal, navigate to `http://localhost:3000` in your browser, and you should see a basic version of your app!

## Adding user interaction

Now we need to add functionality to fetch the full launch data when a user clicks on an item in the panel. We will create a hook in the `App` component to track the flight ID and pass it to the `LaunchProfile` component to refetch the launch data.

In our `src/App.tsx`, we will add `useState` to maintain and update the state of the ID. We will also use `useCallback` named `handleIdChange` as a click handler to update the ID when a user selects one from the list. We pass the `id` to `LaunchProfile`, and we pass `handleIdChange` to the `<LaunchList />`. 

Your updated `<App />` component should now look like the following:

```jsx
const App = () => {
  const [id, setId] = React.useState(42);
  const handleIdChange = React.useCallback(newId => {
    setId(newId);
  }, []);

  return (
    <div className="App">
      <LaunchList handleIdChange={handleIdChange} />
      <LaunchProfile id={id} />
    </div>
  );
};
```

Inside the `LaunchList.tsx` component, we need to create a type for `handleIdChange` and add it to our props destructuring. Then, on our `<li>` flight item, we will execute the function in the `onClick` callback.

```jsx
export interface OwnProps {
  handleIdChange: (newId: number) => void;
}

interface Props extends OwnProps {
  data: LaunchListQuery;
}

// ...
const LaunchList: React.FC<Props> = ({ data, handleIdChange }) => (
  
// ...
<li
  key={i}
  className={`${className}__item`}
  onClick={() => handleIdChange(launch.flight_number!)}
>
```

Inside `LaunchList/index.tsx`, be sure to import the `OwnProps` declaration to type the `props` being passed to the container component, and then spread the props into the `<LaunchList data={data} {...props} />`.

The final step is to `refetch` the data when the `id` changes. Inside the `LaunchList/index.tsx` file, we will use the `useEffect`, which manages the React lifecycle, and trigger a fetch when the `id` changes. The following are the only changes required to implement the fetch:

```jsx
interface OwnProps {
  id: number;
}

const LaunchProfileContainer = ({ id }: OwnProps) => {
  const { data, error, loading, refetch } = useLaunchProfileQuery({
    variables: { id: String(id) },
  });
  React.useEffect(() => {
    refetch();
  }, [id]);
```

Since we have separated the presentation from the data, we don’t need to make any updates to our `<LaunchProfile />` component; we only need to update the `index.tsx` file so that it refetches the full launch data when the selected `flight_number` changes.

Now you have it! If you followed the steps, you should have a fully functioning GraphQL app. If you got lost anywhere, you can find a working solution in the [source code](https://github.com/treyhuffine/graphql-react-typescript-spacex).

## Conclusion

Once the app is configured, we can see that development speed is incredibly fast. We can build a data-driven UI with ease. GraphQL allows us to define the data that we need in components, and we can seamlessly use it as props within our components. The generated TypeScript definitions give extremely high confidence in the code we write.

If you are looking to dive deeper into the project, the next steps would be to add pagination and more data connectivity using additional fields from the API. To paginate the launch list, you would take the length of your current list and pass the `offset` variable to the `LaunchList` query. I encourage you to explore it deeper and write your own queries so that you can solidify the concepts presented.

_If you found this article helpful, [follow me](https://medium.com/@treyhuffine) for more articles on React, Node.js, JavaScript, and open source software! You can also find me on [Twitter](https://twitter.com/treyhuffine)._
* * *
## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps

[![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)](https://logrocket.com/signup/)  
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

[Try it for free](https://logrocket.com/signup/).
* * *
The post [Build a GraphQL + React app with TypeScript](https://blog.logrocket.com/build-a-graphql-react-app-with-typescript/) appeared first on [LogRocket Blog](https://blog.logrocket.com).