awslabs / aws-mobile-appsync-sdk-js

JavaScript library files for Offline, Sync, Sigv4. includes support for React Native
Apache License 2.0
921 stars 266 forks source link

Optimistic UI updates using helper do not work with queries generated by AWS Amplify #390

Open janhesters opened 5 years ago

janhesters commented 5 years ago

Do you want to request a feature or report a bug? Bug

What is the current behavior? When doing a create mutation, the query is not re run and there are no optimistic UI updates. I have to refresh the app to see the result of the mutation in the list.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.

I followed this tutorial by @dabit3 on YouTube, but instead of React Native I used React. The thing is that when this tutorial was recorded, the graphql queries and mutations where generated with the gql tag (around 40:50 in the video). This is not the case any more. Therefore, here are the ...

Steps to Reproduce

  1. Create a new React app: npx create-react-app my-app && cd my-app.
  2. Initialise AWS Amplify: amplify init. Choose defaults.
  3. Add auth with defaults: amplify add auth and hit yes to all.
  4. Add api: amplify add api and edit the schema like this:
type Todo @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  title: String!
  description: String
}
  1. Add the neccessary packages:
yarn add aws-appsync aws-appsync-react graphql-tag react-apollo aws-amplify aws-amplify-react
  1. Configure index.js:
import Auth from '@aws-amplify/auth';
import Amplify from '@aws-amplify/core';
import AWSAppSyncClient from 'aws-appsync';
import { Rehydrated } from 'aws-appsync-react';
import { ApolloProvider } from 'react-apollo';
// rest of the imports

import config from './aws-exports';
Amplify.configure(config);

const client = new AWSAppSyncClient({
  url: config.aws_appsync_graphqlEndpoint,
  region: config.aws_appsync_region,
  auth: {
    type: config.aws_appsync_authenticationType,
    jwtToken: async () =>
      (await Auth.currentSession()).getIdToken().getJwtToken(),
  },
});

const WithProvider = () => (
  <ApolloProvider client={client}>
    <Rehydrated>
      <App />
    </Rehydrated>
  </ApolloProvider>
);

ReactDOM.render(<WithProvider />, document.getElementById('root'));
  1. Add authenticator and graphql to App.js:
import { withAuthenticator } from 'aws-amplify-react';
import { graphqlMutation } from 'aws-appsync-react';
import gql from 'graphql-tag';
import React, { useState } from 'react';
import { compose, graphql } from 'react-apollo';

import { createTodo } from '../../graphql/mutations';
import { listTodos } from '../../graphql/queries';

const ListTodos = gql(listTodos);
const emptyTodo = { title: '', description: '' };

function App({ createTodo, todos }) {
  const [newTodo, setNewTodo] = useState(emptyTodo);

  const handleChange = e => {
    e.persist();
    setNewTodo(t => ({ ...t, [e.target.name]: e.target.value }));
  }

  const handleClick = () => {
    createTodo({ input: newTodo });
    setNewTodo(emptyTodo);
  }

  return (
    // inputs and button for these functions
    // mapping over todos and rendering them
    // make sure to replace null returned from the queries, 
    // otherwise the app might crash depending on how you render.
  )
}

export default withAuthenticator(compose(
  graphqlMutation(gql(createTodo), ListTodos, 'Todo'),
  graphql(ListTodos, {
    options: { fetchPolicy: 'cache-and-network' },
    props: ({ data }) => ({
      contacts: data.listTodos
        ? data.listTodos.items
        : [],
    }),
  })
)(App));

Checking local storage in the chrome dev tools (under applications) I also found that all todos are there twice. It seems like the todos get added to the local storage and the request to the API is being made, but neither is the UI updated nor is the temporary todo for the optimistic response deleted. Weird is that each todo is still just rendered once, so it seems like the cache's data isn't even taken into account, but I can't say for sure.

What is the expected behavior?

The UI should update optimistically and you shouldn't have to refresh.

Which versions and which environment (browser, react-native, nodejs) / OS are affected by this issue? Did this work in previous versions?

browser on MacOS with Google Chrome. I don't know if this worked before.

dabit3 commented 5 years ago

@janhesters I think the issue may be with the filters / optional args in the auto-generated GraphQL operations.

Can you show us the definitions for createTodo & listTodos?

janhesters commented 5 years ago

@dabit3 Of course! Thank you for your answer.

Here you go:

export const listTodos = `query ListTodos(
  $filter: ModelTodoFilterInput
  $limit: Int
  $nextToken: String
) {
  listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
    items {
      id
      title
      description
    }
    nextToken
  }
}
`;

export const createTodo = `mutation CreateTodo($input: CreateTodoInput!) {
  createTodo(input: $input) {
    id
    title
    description
  }
}
`;
dabit3 commented 5 years ago

Ok, try:

export const listTodos = `query ListTodos {
  listTodos {
    items {
      id
      title
      description
    }
    nextToken
  }
}
`;

For some reason I think the graphqlMutation helper does not work with the arguements. Not sure why, maybe @undefobj or @manueliglesias may have some insight.

Also, I think you may need graphql-tag (not sure if you're already using it).

Either way, let me know if that works.

janhesters commented 5 years ago

@dabit3

Also, I think you may need graphql-tag (not sure if you're already using it).

I am (in step 7):

const ListTodos = gql(listTodos);

Either way, let me know if that works.

I'm gonna try it now and get back to you in a couple of minutes.

janhesters commented 5 years ago

@dabit3 That works!

For some reason I think the graphqlMutation helper does not work with the arguements.

You are right, it does seem like the graphqlMutation helper does not work with the code generated by AWS Amplify.

Thank you for your help, Nader! πŸ™ Writing the mutation manually is still shorter and easier than writing the boiler plate for the optimistic response πŸ‘ŒπŸ»

evelant commented 5 years ago

I'm also running into strange behavior trying to use the graphqlMutation helper. I think this is all related. In addition to the behavior @janhesters is seeing I also get:

It gives warnings that fields are missing when they are not like #350.

It also throws an error "In this environment the sources for assign MUST be an object" unless I add a random key to the object besides input.

This crashes: this.props.createTask({ input: { title: this.state.text || "new todo", xp: 0, type: TASK_TYPE.TASK, completionCount: 0 } })

This works: this.props.createTask({ input: { title: this.state.text || "new todo", xp: 0, type: TASK_TYPE.TASK, completionCount: 0 }, keyForNoReasonAtAllOrCrash: "harr" })

Is amplify/appsync intended to be production ready? The documentation seems all over the place and even the tutorials are broken. I love the concept but I'm having a really tough time getting started due to these issues.

janhesters commented 5 years ago

@AndrewMorsillo To address your last question: Whether AWS Amplify is production ready depends on your needs.

Depending on your use case there might be essential features missing. And the docs could use a lot of improvement. And also depending on your use case AWS Amplify might enable you to build great things without having to code up a backend. We are using it in production, even thought there are quirky errors and bugs like this.

robertvorthman commented 5 years ago

From the AWS Amplify docs on offline mutations:

use the buildMutation and buildSubscription built-in helpers that are part of the AppSync SDK (we strongly recommended using these helpers).

But these helpers don't seem to work with Amplify generated GraphQL statements (which use input variables). Is there any way to get this repo's tutorial to work with Amplify or should I create my own schema without Amplify's codegen?