Josh Pittman
Josh Pittman

Reputation: 7324

How do I set typescript types for an AWS amplify GraphQL response in React?

I have a react component in typescript and I want to set the results of an appsync graphql query to a property in state.

import React, { Component } from 'react';
import { API, graphqlOperation } from 'aws-amplify';
import {ListProjectsQuery} from './API'
import {listProjects } from './graphql/queries';

class App extends Component<{}, {
  projects:ListProjectsQuery
}>  {
  state = {
    projects: null
  };
  async componentDidMount() {
    const projects = await API.graphql(graphqlOperation(listProjects));
    this.setState({ projects });
  }

  ...

How do I define the default state property to make this work?

I found a similar problem in the amplify github issues but that solution was in the context of a stateless functional component. I am using a stateful component.

Depending on what I try I seem to get one of three errors.

The code above throws Type 'null' is not assignable to type 'ListProjectsQuery'..

This makes sense so I try and map the shape in state like so:

state = {
    projects: {listProjects: {items: [{name: ''}]}}
  }

which makes it throw Types of property 'projects' are incompatible.

I either get told that a Property does not exist on type 'Observable<object>' or I get told the shape of the default state value is not compatible.

Finally I tried to use an interface like in the example I found:

interface IListProjectQuery {
  projects: ListProjectsQuery;
}

and then I reference the interface

class App extends Component<
  {},
  {
    projects: IListProjectQuery;
  }
> 

and it throws the following error Type '{ projects: null; }' is not assignable to type 'Readonly<{ projects: IListProjectQuery; }>'.

What value do I give the default state property in order for it to make typescript happy?

The ListProjectsQuery import is autogenerated by amplify/appsync codegen, the type alias looks like so:

export type ListProjectsQuery = {
  listProjects:  {
    __typename: "ModelProjectConnection",
    items:  Array< {
      __typename: "Project",
      id: string,
      name: string,
      organisation:  {
        __typename: "Organisation",
        id: string,
        name: string,
      } | null,
      list:  {
        __typename: "ModelListConnection",
        nextToken: string | null,
      } | null,
    } | null > | null,
    nextToken: string | null,
  } | null,
};

Upvotes: 5

Views: 4236

Answers (2)

Hackman
Hackman

Reputation: 2659

  • You have to define a type that matches the structure of data you are expecting from amplify
  • The second step is to cast your response data as the type you defined and that's it.
  • Your projects property inside your state should be properly typed, projects: IProject[ ] | undefined . You either have an array of projects or it's undefined.

export type IProject = {
    name: string
}

export type GetProjectsQuery = {
    listProjects:{
        items: IProject[]
        nextToken: string
    }
}

const fetchAllProjects = async () => {
    try {
      const result = (await API.graphql(graphqlOperation(queries.listProjects))) as {
        data: GetProjectsQuery
      }
      projects = result.data.listProjects.items
      

    } catch (error) {
      //handle error here 
    }

Upvotes: 7

Mahmood Dehghan
Mahmood Dehghan

Reputation: 8265

Make your property optional:

class App extends Component<{}, {
  projects?: ListProjectsQuery //<<== note the question mark
}>  {
  state = {
    projects: null
  };
  async componentDidMount() {
    const projects = await API.graphql(graphqlOperation(listProjects));
    this.setState({ projects });
  }

Upvotes: 1

Related Questions