react-apollo + Codegen + TypeScript, How You Can Compose Multiple Queries/Mutations to a Component

piglovesyou

Soichi Takamura

Posted on April 7, 2019

react-apollo + Codegen + TypeScript, How You Can Compose Multiple Queries/Mutations to a Component

Apollo team already provides decent documentation that lets you type your components with types generated from Codegen.

This is a quick tutorial to go further where you compose multiple GraphQL Query/Mutation to bind to a single component.

Summary

  • Define from outer HOCs (e.g. withData function) to inner HOCs
  • Pass with-data-props to a child HOC
  • Do not use compose() function

Example

SUPPOSE this is a new student creation form that requires pulldown schools list. Schools are resolved through GraphQL Query. Also, form submission is done through GraphQL Mutation.

// UserCreate.tsx

import {ChildDataProps, ChildMutateProps} from 'react-apollo';
import {
  CreateUserMutation,
  CreateUserMutationVariables,
} from './__generated__/CreateUserMutation';
import { SchoolsQuery } from './__generated__/SchoolsQuery';
import { CreateUserInput } from "../../../../__generated__/globalTypes";

type ComponentProps = { title: string, };
type WithDataProps = ChildDataProps<ComponentProps, SchoolsQuery, {}>;
const withData = graphql<ComponentProps, SchoolsQuery, {}, WithDataProps>(gql`
  query SchoolsQuery {
    schools {
      id
      email
      nameJa
      nameEn
      phone
    }
  }
`);

type WithMutateProps = ChildMutateProps<WithDataProps, CreateUserMutation, CreateUserMutationVariables>;
const withMutate = graphql<WithDataProps, CreateUserMutation, CreateUserMutationVariables, WithMutateProps>(gql`
  mutation CreateUserMutation($input: CreateUserInput!) {
    createUser(input: $input) {
      email
      nameEn
      nameJa
      school
    }
  }
`);

const UserCreate = withData(withMutate(props => {
  const {
    mutate: createUser,
    data: {schools},
  } = props;
  return (
    <Container>
      <h1>Create New User</h1>
      <Form
        method="post"
        onSubmit={async (e: any) => {
          e.preventDefault();

          const input = (createFormData(e.target) as CreateUserInput ) ;

          const result = await createUser({
            refetchQueries: [
              ({query: UsersQueryGQL} as PureQueryOptions)
            ],
            variables: {input},
          });
          if (!result || !result.data) throw new Error('Mutation failed.');

          const {
            data: {createUser: created},
          } = result;

          // Do something with created user data

          history.push('/users');
        }}
      >
        <FormGroup>
          <Label for="email">User email address:</Label>
          <Input id="email" type="text" name="email"/>
        </FormGroup>

        <FormGroup>
          <Label for="nameJa">User name (Japanese):</Label>
          <Input id="nameJa" type="text" name="nameJa"/>
        </FormGroup>

        <FormGroup>
          <Label for="nameEn">User name (English):</Label>
          <Input id="nameEn" type="text" name="nameEn"/>
        </FormGroup>

        {schools && (
          <>
            <FormGroup>
              <Label for="school">school</Label>
              <p>
                <select id="school" name="school">
                  <option>---please select---</option>
                  {schools.map(s => (
                    <option key={s.id} value={s.id}>
                      {s.nameEn}
                    </option>
                  ))}
                </select>
              </p>
            </FormGroup>
          </>
        )}

        <FormGroup>
          <Button color="primary" type="submit" block>
            Log in
          </Button>
        </FormGroup>
      </Form>
    </Container>
  );
}));

export default UserCreate;

In Outer HOC

type WithDataProps = ChildDataProps<ComponentProps, SchoolsQuery, {}>;
const withData = graphql<ComponentProps, SchoolsQuery, {}, WithDataProps>(/*...*/);
  • The first argument in generic arguments of graphql() is used as actual acceptable properties
  • The WithDataProps is used as types we pass to the next inner component
    • So this is what we should accept in the inner withMutate.

In Inner HOC

type WithMutateProps = ChildMutateProps<WithDataProps, CreateUserMutation, CreateUserMutationVariables>;
const withMutate = graphql<WithDataProps, CreateUserMutation, CreateUserMutationVariables, WithMutateProps>
  • Accept props WithDataProps, passed from the outer HOC withData
    • which already includes ComponentProps, so the final props the component consumes is compound of 3: Query data types, Mutation types and component own props.

compose function?

As of react-apollo version 2.5.2, compose is not typed.

export declare function compose(...funcs: Function[]): (...args: any[]) => any;

I wrote this post because I couldn't find any example to do that. Hope it helps someone.

💖 💪 🙅 🚩
piglovesyou
Soichi Takamura

Posted on April 7, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related