Apollo is a GraphQL client to query and mutate GraphQL APIs. In this tutorial, we are going to go over how to use it with React (hooks) and TypeScript. By utilizing generated types with fragments, we are going to take our development experience to the next level.
Installation
npm i @apollo/react-hooks apollo-boost graphql
Add to React
We can add Apollo to React by wrapping our root App
component with the ApolloProvider
and provide it an instance of the ApolloClient
.
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient from 'apollo-boost';
export const client = new ApolloClient({
uri: `${process.env.REACT_APP_API_URL}/graphql`
});
const App = () => (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
Query in React
Now our React application is ready to start using hooks. We can query our GraphQL API with the useQuery
hook.
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';
const PACK_QUERY = gql`
query PackDetailsPagePackQuery($packId: ID!) {
currentUser {
id
}
pack(id: $packId) {
id
name
description
imageUrl
user {
id
}
}
}
`;
// In our React component
const { data, loading } = useQuery(PACK_QUERY)
Types
Now this is great, but it would be awesome if we could have the response typed. We are using TypeScript after all. Good news is that TypeScript and GraphQL is a match made in heaven. What's even better is that Apollo provides a set of tools that makes integrating the two technologies a breeze. Let's first download Apollo tools:
npm i apollo --save-dev
Then we can create two commands in our package.json
. First command is introspect-schema
which makes a request to our GraphQL API and generate a schema.json
file for our Apollo client to use. The second command is gql-gen
commmand which first calls the instrospect-schema
command and then looks at all TypeScript files to generate types for queries, mutations, and fragments (will expand more on fragments later).
"introspect-schema": "apollo schema:download --endpoint=http://localhost:4000/graphql schema.json" ,
"gql-gen": "npm run introspect-schema && apollo codegen:generate --localSchemaFile=schema.json --target=typescript --includes=src/**/*.tsx --tagName=gql --addTypename --globalTypesFile=src/__generated__/graphql-types.ts __generated__"
Now we can run the following command to have types generated for us:
npm run gql-gen
Once the types are generated, we can import it in our React component:
import { PackDiscoverPageQuery } from "./__generated__/PackDiscoverPageQuery";
And then specify the type when calling the useQuery
hook.
const { data, loading } = useQuery<PackDiscoverPageQuery>(PACKS_QUERY);
Mutations
Mutations work similarly to queries. There is a useMutation
hook we can use and types will be generated for us.
import { PackDetailsPageGameCreateMutation } from "./__generated__/PackDetailsPageGameCreateMutation";
const GAME_CREATE = gql`
mutation PackDetailsPageGameCreateMutation($input: GameCreateInput!) {
gameCreate(input: $input) {
code
}
}
`;
const [gameCreate] = useMutation<PackDetailsPageGameCreateMutation>(GAME_CREATE);
const handleCreate = () => {
const { data } = await gameCreate({
variables: { input: { packId: packId || "" } }
});
// ...
}
Fragments
Fragments will help us build reusable components. By specifying data requirements along with the component, we can combine all of the data requirements of a single page (combination of fragments from all components) and get the data in a single request.
For example, let's take a look at this following query:
const PACK_QUERY = gql`
query PackCreatorPagePackQuery($packId: ID!, $actId: ID) {
pack(id: $packId) {
id
name
acts(first: 100) {
edges {
node {
id
question
answer
instruction
questionType {
id
slug
}
answerType {
id
slug
}
}
}
}
}
act(id: $actId, packId: $packId) {
id
question
answer
instruction
questionType {
id
slug
}
answerType {
id
slug
}
}
}
`;
This query is very long and it is not clear which components need what data. When we use fragments, it becomes clear which components need what data.
const PACK_QUERY = gql`
query PackCreatorPagePackQuery($packId: ID!, $actId: ID) {
pack(id: $packId) {
...NavigationPackFragment
...SidebarPackFragment
}
act(id: $actId, packId: $packId) {
...ActPreviewFragment
}
}
${Navigation.fragments.pack}
${Sidebar.fragments.pack}
${ActPreview.fragments.act}
`;
We can define fragments with the components like the following:
Navigation.fragments = {
pack: gql`
fragment NavigationPackFragment on Pack {
id
name
}
`
};
Sidebar.fragments = {
pack: gql`
fragment SidebarPackFragment on Pack {
id
acts(first: 100) {
edges {
node {
id
question
answer
instruction
questionType {
id
slug
}
answerType {
id
slug
}
}
}
}
}
`
};
ActPreview.fragments = {
act: gql`
fragment ActPreviewFragment on Act {
id
question
answer
instruction
questionType {
id
slug
}
answerType {
id
slug
}
}
`
}
Our gql-gen
script create types for our fragments as well which we can use to declare prop types in our components.
import { ActPreviewFragment } from "./__generated__/ActPreviewFragment";
type Props = {
act: ActPreviewFragment;
};
const ActPreviewFragment = ({ act }: Props) => {
// ...
}
Top comments (1)