Generate Strongly Typed React Components with GraphQL

Mat Warger AWS, Development Technologies, GraphQL, JavaScript, Programming 1 Comment

Attention: The following article was published over 6 years ago, and the information provided may be aged or outdated. Please keep that in mind as you read the post.

TL;DR: Using GraphQL? You can generate strongly-typed React components — read on!

When developing in React, using a type system (like Typescript or Flow) can be a great help. You can be sure that your props and state are what you expect, at build-time, and code your components to match. But what happens when you’re calling to an API to fetch some data, and the shape of that data is what really matters? Maybe the data get passed as props to a child component? You can create types for this, sure, but are they correct? Probably not! Or at least, probably not for long! Things change. Wouldn’t it be great if your types changed too?

The thing is, there is already a great way to get data that is based on type definitions. It’s called GraphQL, and it’s awesome (if you’re new to this, check out an introduction). Your schema holds the types, and your queries return data in a set shape defined by the client — not arbitrarily from the server. This is a pretty nice benefit. Maybe we could use the schema and queries to properly generate types that we can use in our client… That would be pretty sick.

“But Mat, I’m not using GraphQL, and I’m only using REST to get my data.” That’s okay, wrap your endpoints. I contend that you should be using GraphQL anyway for reasons we will see here. (Update: While writing this, AppSync launched HTTP resolvers. If you’re using AppSync like I am, give that a look as well!)

Types enable tooling. Tooling enables integrations that are not otherwise possible. This is what makes GraphQL bigger than GraphQL — it enables an ecosystem. In a similar way, React enabled an ecosystem around component-based architectures. Most modern frameworks now implement the same patterns that React pioneered in one-way data flow and UI as a function of state. The ecosystem in each of these cases is where things get interesting.

In this post, we’re going to take a simple component from zero type awareness to fully typed, with local variables and GraphQL queries included, with a simple workflow. Grab a cup of coffee and a snack, and let’s see how this we can use GraphQL to generate type-safe components in React.

From here on out, I’m going to assume a basic familiarity with React, Apollo, and GraphQL. If you need a jumpstart on this beyond the examples provided here, check out these great tutorials. If you have any problems, hit me up in the comments below.

Project Setup

Note: I’m going to use a hosted GraphQL platform called AppSync on AWS for this. If you don’t have an AWS account or don’t know AppSync, no worries! I’ve tried to put all the relevant information in code snippets and images in this article. As long as you have a basic understanding of React and GraphQL, you can still follow along — just skip to the next section entitled “Typing your Javascript”.

If you’d like to follow along and get the hang of the workflow by coding, you’re going to need a GraphQL endpoint or schema (again, this is not necessary, I’ve supplied a sample schema in the example project — if you just want to use that, skip to the next section). One of the quickest ways to get going with your own endpoint is to use a hosted GraphQL service. For this, I recommend AppSync. You can learn more about AppSync here. We’re going to use the sample “Events API” project that is provided by AppSync.

To set this up, head over to AppSync and log in. Click on the big orange “Create API” button. Choose the “Event App” from the list of choices. You can change the API name to whatever you like. Scroll down and hit the orange “Create” button. AppSync will go to work provisioning some resources for you, noted by the blue bar at the top of the window.

 

When that’s finished, we’ll follow the instructions at the bottom of the window to integrate the API with a project.

  1. Run git clone to pull down the project code.

git clone https://github.com/aws-samples/aws-mobile-appsync-events-starter-react

  1. Download the AppSync config file and place it in the src directory of the project you just cloned.

  2. Click the Export Schema dropdown and choose the schema.graphql file. Place this into the src folder as well.

Next, cd into the project directory and install the dependencies with npm (or yarn, if that’s your thing):

cd .\aws-mobile-appsync-events-starter-react\
npm install

Once the install is complete, run npm start and you should see your application startup, showing something like this:

We can begin with the AllEvents component (found in the js file of the same name in the example project). Towards the bottom of the file, there’s a section that uses the compose function to initialize the QueryAllEvents query. We’re going to use the Apollo Query component for this instead, so comment this section out.

You can also remove the the line that destructures events from props and the section that attempts to map over the events object to render them, as it has just been removed. We will restore this functionality later.

Remove events prop…

Comment out map…

Your app should now be good to go. Next, let’s look at adding type support.

Typing Your JavaScript

If you’ve skipped from above, and want to begin from here without setting anything up in Amazon, you can clone the sample project to check it out.

Typing your javascript can bring many benefits, as we’ll see throughout this walkthrough. We’re going to be using Flow. You can also use Typescript and get the same benefits (although the setup will be more complex, and not covered here). I like flow because of the way it allows for more opt-in of types when you want them, the simpler configuration, speed, and how, in some cases, it can provide benefits without even adding any types to your code. This project uses create-react-app, so to add flow, just follow the instructions here.

Once that’s done, you should be able to run npm run flow in the root of your project to see any errors. However, we haven’t marked any files yet, so you won’t see any results.

At the very top of AllEvents.js, add the // @flow declaration to enable type-checking on its contents. It should look like this:

There are a few ways you can get your code checked for errors. Like before, you can run npm run flow and see the output in a console. This will flow over all the code that has been declared as checkable with // @flow and tell you where any errors are. You can also install a VS Code extension if that’s your style (I use this and find it very helpful). If you do install and configure the extension, you will not need to run the console commands. You will simply see the type-checking in your editor as red squiggles.

Run <code>npm run flow</code> in a command prompt in the root of the project. You should see something like this:

… along with a bunch of errors related to the busy property of state, among others.

At some points, you may get erroneous errors from your node_modules folder. These can be safely ignored.

Great! This means flow has found our code and tells us that we should have types for our props and state. To fix this, we can declare a type that let flow know what we’re expecting here.

Right above the class declaration for the component, add the type definition for the props and state. We’ll call it AllEventsProps and AllEventsState.

We use this in our component by passing it as type arguments to our Component, like this:

Run npm run flow again. There’s one last error, and it’s concerning the coercion of a boolean value in the classname of the “Sync with Server” link. Let’s update this to be a ternary instead (Side note: I like ternaries in general because they force you to handle “both sides” of an if).

Run npm run flow again and check your results. You should see No Errors! after the run. Good!

This example handles the issues that applied to the AllEvents component, but this same pattern is all you need to do to add types to any data in your application. Flow will check it for you and make sure you’re using it correctly.

To be able to utilize types for our GraphQL queries, we’ll need to convert this component to use the Apollo Query component instead of the having the query data passed in as props from the higher-order function. Let’s look at that next.

Apollo Query

First up, let’s handle the import we’re going to need. Add Query to the import line for react-apollo at the top of AllEvents.js.

import { graphql, compose, withApollo, Query } from 'react-apollo'

We will use the Query defined QueryAllEvents.js, albeit with a few modifications. We need to remove the parentheses and add a name to the query. This is important for the type generation we’ll do in a minute.

Next, add the Query declaration right beneath the commented out events map from before.

For now, we’re just going to console.log the data that we get back from the query. If you haven’t already, go ahead and use the app to add an event or two. You should see your events in the console after you refresh.

This accomplishes exactly the same thing as before, but now we’re using the Apollo Query component. It’s that easy! We’ll be able to use this as a base to help us apply types to our Query to make sure we’re using our data correctly.

Generating GraphQL Types

Next up, let’s see what it takes to generate types from our Queries.

I’m using AppSync as my GraphQL server, so I’m going to install a package called aws-appsync-codegen to generate the types that we’ll use a little later. If you’re using your own server, or another hosted service like Prisma (formerly Graphcool), you can also use the apollo-codegen (recently, this has been rolled into apollo-cli).

Install the aws-appsync-codegen utility by running npm install --save-dev aws-appsync-codegen. In your package.json, add the following two entries to the scripts block:

"introspect-schema": "aws-appsync-codegen introspect-schema ./src/schema.graphql --output ./src/schema.json",

"generate-types": "aws-appsync-codegen generate src/**/*.js --schema ./src/schema.json --target flow --output ./src/graphql-types.flow.js --add-typename"

The first entry introspect will read the schema.graphql file that is local to our project (we downloaded this in the project setup — if you cloned the project from my github beginning branch, it was already included). You can also point this at a remote GraphQL endpoint to accomplish the same thing. This will produce the output file schema.json. This is used as the input to our type generator script.

The generate-types entry is where the magic happens. This takes the schema.json that was generated, and the .js files we have in our project as input. We set the target as flow to generate types usable by flow, and set the output to a file called graphql-types.flow.js. You can rename this to whatever you want, but I’ll leave it like this for now. It will use these files to generate the types we can use in our components!

Run those two commands, first

npm run introspect-schema

then

npm run generate-types

If all goes well, you should be left with a schema.json and graphql-types.flow.js in the src folder of the project. This is the workflow you will follow whenever your schema is updated. Just rerun these two commands and you’re good to go. Now would be a good type to add the schema.json file to your .gitignore.

Take a look at the generated types file:

It even includes comments from the schema!

This contains a type that should look relatively familiar. This was created from the gql tag in our QueryAllEvents.js file! Now we can apply this type to our Query component and be sure that our components are behaving properly.

Typed Query Components with Apollo

We’ll take some inspiration from the Apollo Typescript Docs for the next bit. First, we need to import the types that we’re going to use. Add the following at the top of AllEvents.js.

import type {
GetEventsQuery
} from '../graphql-types.flow'

Beneath this, we need to add a class declaration that allows us to pass types to our Query component.

class EventsQuery extends Query {}

Finally, replace the Query component declaration in our component with EventsQuery. If you run flow again, you’ll see that… nothing happened. It didn’t work.

I don’t know why. Except to suspect that you could be out of luck if you’re using flow and expecting the Apollo components to work out of the box. But we’re not, because we have a work-around for this that results in a much more customizable solution. Thanks to React and Flow, we can make our own component and get the help we want with Types.

Our Own Typed Query Component

Create a new file called TypedQuery.js. Add the following code.

https://gist.github.com/mwarger/e87724618be33e37daff51f016b15614

Here we’re using React to create a Typed component class to handle the types that we want to enable for our Query. In the render method, you can see that we’re simply wrapping a normal apollo Query component. The TypedQueryProps object is what we use to pass our generic types to the query. This gives us the type safety we desire. While we’re at it, we introduce a generic way to handle the loading and error properties that are provided by the Query child-function. For more information about using Generic Types in flow, check out the docs.

Return to the AllEvents.js and let’s put this to use. Add the import at the top.

import TypedQuery from './TypedQuery';

Next, replace the Query declaration with the new TypedQuery we added.

Run npm run flow. Do you see errors? Yes! The VS Code extension gives me some awesome red squiggles. Hovering over them reveals the problem. The key thing about this is that we are able to know that our app would potentially fail without needing to run it and find out. This is the advantage of using types for our components as well as the shape of our data.

Let’s get to fixing these, step by step. First, our function is now passing only data down to the child-function prop — this is because we’re handling the loading and error states in the TypedQuery component itself.

Let’s first attempt a fairly naive implementation. We grab the data.listEvents.items property as it’s described in the type.

Flow is going to let us know that we can’t just grab the items, because listEvents might be null or undefined. It will warn us to check first, so let’s do that. We’ll need to check events also — because it might also not exist.

There we go — we’re handling the falsy possibilities.

Now, the nice thing about flow is that it will crawl through your code and find instances of potential failure for you. Check out the renderEvent function:

We didn’t explicitly mark the type, but it was able to find it through the mapping function we used in the render method.

Here’s the error that flow shows me for handleDeleteClick, which is also gathered as a potential problem based on the event that was passed in:

We can add some simple checks to these to make sure that values exist before we access them.

First, for renderEvent — make sure event exists before rendering the rest of the component:

Further down, make the same safety checks with the comments:

And finally, for the handleDeleteClick method, we can check that the event exists before we prompt the user about deleting it:

All clear! If you run npm run flow again or check your extension, you shouldn’t see any more errors. Our custom component and flow types saved the day. We now know that our component will work. We can have confidence in that. We have handled the possible situations in which any nulls or undefined values would cause unexpected behavior and we know exactly what properties are available on our data and can reference them accordingly. Anytime your queries change, you can rerun the generator and your component type information will be updated. This can inform the changes you need to make to your components as the shape of your data evolves.

We can create components that behave exactly as we expect… and we haven’t even ran the application!

If you’ve been following along, try and run your application, and check out your events. I guarantee you it will work. How’s that for a rock-solid component?

Onward!

I hope this helped you get a better understanding of how and, more importantly, why you should be using a type system with your React and GraphQL applications. My goal was to provide a gentle introduction to how types can bring clarity from your backend data to your frontend client application. This will allow you to approach component development with confidence. Give it a try, and let me know what you think!

Resources

You can find all the relevant code and finished project here. PR’s are welcome if you spot any problems or want to suggest improvements!

The Flow docs themselves are a great place to get familiar the type system. Generics are a great way to make generalized components as we’ve seen here. I’m by no means an expert, so if you see any way to make improvements, please leave a comment below or ping me on twitter.

If you’re on the Typescript train, the handbook is a good spot to get a refresher on the basics.

I’m using AWS AppSync for this project.

Thank you for reading!

0 0 votes
Article Rating
Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments