Amplify-js: DataStore queries don't do anything on the first load of the application

Created on 3 Jun 2020  路  14Comments  路  Source: aws-amplify/amplify-js

Describe the bug
After newest update, DataStore queries don't do anything on the first load of the react-native application. Only on the second load, it starts to work properly.

To Reproduce
Steps to reproduce the behavior:

  1. Open react-native application
  2. Sign in with a user
  3. Try to get some data with DataStore.query(Something)
  4. Don't get anything (well it spits empty arrays, so I don't think that it does anything good)
  5. Close application.
  6. Open application again
  7. Now everything works

Expected behavior
Expecting some data on the first load, as it worked before.

Code Snippet
Standard DataStore.query(SomeDataToQuery)

What is Configured?
20200603_155241

20200603_155423

It worked on this version:

    "@aws-amplify/api": "3.1.12",
    "@aws-amplify/auth": "3.2.9",
    "@aws-amplify/cache": "3.1.12",
    "@aws-amplify/core": "3.2.9",
    "@aws-amplify/datastore": "2.1.2",
    "@aws-amplify/pubsub": "3.0.13",
    "@aws-amplify/storage": "3.2.2",

Now it doesn't:

    "@aws-amplify/api": "3.1.14-unstable.2",
    "@aws-amplify/auth": "3.2.11-unstable.2",
    "@aws-amplify/cache": "3.1.14-unstable.2",
    "@aws-amplify/core": "3.3.1-unstable.2",
    "@aws-amplify/datastore": "2.2.1-unstable.2",
    "@aws-amplify/pubsub": "3.0.15-unstable.2",
    "@aws-amplify/storage": "3.2.4-unstable.2",

Additional context
Few other guys are getting the same issue, you can find more on discord channel for amplify-js.
Running a react-native application.

DataStore question

Most helpful comment

Hi @Darapsas and @nubpro

When enabling syncing to the cloud on the DataStore, queries you run might yield empty or incomplete results if the initial sync process hasn't completed or there is not enough data synced locally at the time of the query. In this scenario, you can either wait for the sync process to finish (by listening to the 'ready' event on the 'datastore' Hub channel) or you can observe a model until you accumulate enough data to render your UI.

Below you'll find these two approaches exemplified:

Waiting for the sync process to finish

import React, { useEffect, useState } from "react";

import { Amplify, Hub } from "@aws-amplify/core";
import { DataStore, Predicates } from "@aws-amplify/datastore";

import awsConfig from "./aws-exports";
import { Note } from "./models";

Amplify.configure(awsConfig);

function App() {
  const [notes, setNotes] = useState([] as Note[]);

  useEffect(() => {
    // Create listener that will stop observing the model once the sync process is done
    const removeListener = Hub.listen("datastore", async (capsule) => {
      const {
        payload: { event, data },
      } = capsule;

      console.log("DataStore event", event, data);

      if (event === "ready") {
        const notes = await DataStore.query(Note, Predicates.ALL, {
          page: 0,
          limit: 15,
        });

        setNotes(notes);
      }
    });

    // Start the DataStore, this kicks-off the sync process.
    DataStore.start();

    return () => {
      removeListener();
    };
  }, []);

  return (
    <div>
      <ul>
        {notes.map((note) => (
          <li key={note.id}>{note.id}</li>
        ))}
      </ul>
    </div>
  );
}

Observing a Model until enough data is available

import React, { useEffect, useState } from "react";

import { Amplify, Hub } from "@aws-amplify/core";
import { DataStore, Predicates } from "@aws-amplify/datastore";

import awsConfig from "./aws-exports";
import { Note } from "./models";

Amplify.configure(awsConfig);

function App() {
  const [notes, setNotes] = useState([] as Note[]);

  // Number of notes we want to show in our initial render
  const limit = 15;

  useEffect(() => {
    let subscription: any = undefined;

    // Create listener that will stop observing the model once the sync process is done
    const removeListener = Hub.listen("datastore", (capsule) => {
      const {
        payload: { event, data },
      } = capsule;

      console.log("DataStore event", event, data);

      if (event === "ready") {
        if (subscription) {
          removeListener();
        }
      }
    });

    // Async IIFE for more convenient async/await syntax
    // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function#Description
    (async () => {
      const notes = await DataStore.query(Note, Predicates.ALL, {
        page: 0,
        limit: limit,
      });

      if (notes.length === limit) {
        setNotes(notes);
        return;
      }

      // If we didn't get enough data from the initial query, observe the model until we do
      subscription = DataStore.observe(Note).subscribe(({ element, ...x }) => {
        notes.push(element);

        if (notes.length === limit) {
          subscription.unsubscribe();

          setNotes(notes);
        }
      });
    })();

    return () => {
      // We clean up the hub listener and the observe subscription

      removeListener();

      if (subscription) {
        subscription.unsubscribe();
      }
    };
  }, []);

  return (
    <div>
      <ul>
        {notes.map((note) => (
          <li key={note.id}>{note.id}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

All 14 comments

Forgot to mention, I tried to use your fancy await DataStore.start() before first queries, didn't seem to do anything.

I'll tag along, same problem here on react native too.

Hi @Darapsas and @nubpro

When enabling syncing to the cloud on the DataStore, queries you run might yield empty or incomplete results if the initial sync process hasn't completed or there is not enough data synced locally at the time of the query. In this scenario, you can either wait for the sync process to finish (by listening to the 'ready' event on the 'datastore' Hub channel) or you can observe a model until you accumulate enough data to render your UI.

Below you'll find these two approaches exemplified:

Waiting for the sync process to finish

import React, { useEffect, useState } from "react";

import { Amplify, Hub } from "@aws-amplify/core";
import { DataStore, Predicates } from "@aws-amplify/datastore";

import awsConfig from "./aws-exports";
import { Note } from "./models";

Amplify.configure(awsConfig);

function App() {
  const [notes, setNotes] = useState([] as Note[]);

  useEffect(() => {
    // Create listener that will stop observing the model once the sync process is done
    const removeListener = Hub.listen("datastore", async (capsule) => {
      const {
        payload: { event, data },
      } = capsule;

      console.log("DataStore event", event, data);

      if (event === "ready") {
        const notes = await DataStore.query(Note, Predicates.ALL, {
          page: 0,
          limit: 15,
        });

        setNotes(notes);
      }
    });

    // Start the DataStore, this kicks-off the sync process.
    DataStore.start();

    return () => {
      removeListener();
    };
  }, []);

  return (
    <div>
      <ul>
        {notes.map((note) => (
          <li key={note.id}>{note.id}</li>
        ))}
      </ul>
    </div>
  );
}

Observing a Model until enough data is available

import React, { useEffect, useState } from "react";

import { Amplify, Hub } from "@aws-amplify/core";
import { DataStore, Predicates } from "@aws-amplify/datastore";

import awsConfig from "./aws-exports";
import { Note } from "./models";

Amplify.configure(awsConfig);

function App() {
  const [notes, setNotes] = useState([] as Note[]);

  // Number of notes we want to show in our initial render
  const limit = 15;

  useEffect(() => {
    let subscription: any = undefined;

    // Create listener that will stop observing the model once the sync process is done
    const removeListener = Hub.listen("datastore", (capsule) => {
      const {
        payload: { event, data },
      } = capsule;

      console.log("DataStore event", event, data);

      if (event === "ready") {
        if (subscription) {
          removeListener();
        }
      }
    });

    // Async IIFE for more convenient async/await syntax
    // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function#Description
    (async () => {
      const notes = await DataStore.query(Note, Predicates.ALL, {
        page: 0,
        limit: limit,
      });

      if (notes.length === limit) {
        setNotes(notes);
        return;
      }

      // If we didn't get enough data from the initial query, observe the model until we do
      subscription = DataStore.observe(Note).subscribe(({ element, ...x }) => {
        notes.push(element);

        if (notes.length === limit) {
          subscription.unsubscribe();

          setNotes(notes);
        }
      });
    })();

    return () => {
      // We clean up the hub listener and the observe subscription

      removeListener();

      if (subscription) {
        subscription.unsubscribe();
      }
    };
  }, []);

  return (
    <div>
      <ul>
        {notes.map((note) => (
          <li key={note.id}>{note.id}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

@manueliglesias When I use the "Observing a Model until enough data is available" method you laid out above, I end up with no data. The only difference in my code is that I pass an ID to the observe method.

So this doesn't seem to be working:

const id = '69ddcb63-7e4a-4325-b84d-8592e6dac07b';

subscription = DataStore.observe(Note, id).subscribe(({ element, ...x }) => {
  notes.push(element);

  if (notes.length === limit) {
    subscription.unsubscribe();

    setNotes(notes);
  }
});

When I remove the ID it works. I cleared the DataStore before testing each time.

I've created a new issue: https://github.com/aws-amplify/amplify-js/issues/6003

Hi @mdoesburg

I went through my code again and try it in fresh apps and it worked as expected, do you have a codesandbox with your code or similar where I can take a closer look at it?

@manueliglesias Yeah sorry about that, it seems like your code does work. It doesn't work when I pass an ID in. I've created a new issue here: https://github.com/aws-amplify/amplify-js/issues/6003

Hello there, @manueliglesias,
So this morning I have upgraded packages to the newest "stable" versions:

    "@aws-amplify/api": "3.1.15",
    "@aws-amplify/auth": "3.2.12",
    "@aws-amplify/cache": "3.1.15",
    "@aws-amplify/core": "3.3.2",
    "@aws-amplify/datastore": "2.2.2",
    "@aws-amplify/pubsub": "3.0.16",
    "@aws-amplify/storage": "3.2.5",
    "amazon-cognito-identity-js": "4.3.1",

And then I tried your code snippet (in theory everything should work, it looks very clean) with Hub.listen (the first one) and then my terminal spits this into my face:
20200604_154059

My code snippet, based on yours:

  useEffect(() => {
    // Create listener that wil stop observing the model once the sync process is done
    const removeHubListener = Hub.listen('datastore', async (capsule) => {
      const {
        payload: { event, data },
      } = capsule

      console.log('DataStore event:::::::::::::::::::::::::::: ', event, data)

      if (event === 'ready' ) {
        console.log(
          'I am ready as hell ---------------------------------------'
        )
      }
    })

    console.log('Trying to start DataStore: ')
    DataStore.start()

    return () => {
      removeHubListener()
    }
  }, [])

Maybe you have any ideas?

@manueliglesias Ok, so I think I found the culprit to why I am getting mixed results. See the following example:

import React, { useCallback, useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
import { DataStore } from '@aws-amplify/datastore';
import { Hub } from '@aws-amplify/core';

import { Contact } from '~/models';

const DataStoreScreen = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const removeListener = Hub.listen('datastore', async ({ payload }) => {
      console.log(payload.event, payload.data);

      if (payload.event === 'ready') {
        console.log('DataStore ready');
        dataQuery();
      }
    });

    console.log('Starting DataStore');
    DataStore.start();

    return () => removeListener();
  }, [dataQuery]);

  const dataQuery = useCallback(async () => {
    console.log('Querying data...');

    const _data = await DataStore.query(Contact);

    console.log(_data);

    setData(_data);
  }, []);

  const clear = () => {
    DataStore.clear();
    setData([]);
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Data</Text>
      <Text>{JSON.stringify(data)}</Text>
      <Button title="Query" onPress={dataQuery} />
      <Button title="Clear" onPress={clear} />
    </View>
  );
};

export default DataStoreScreen;

I get data when I query, but after clearing the DataStore queries stop working. Hard reloading after clearing shows the data again, but my app clears the DataStore when a user logs out, so this issue is problematic for me.

@mdoesburg There's a PR that might help you, #6010

Once it is merged, you will be able to try it on @unstable

@manueliglesias I tried @unstable, and get no events when listening to 'datastore' on the Hub, after running DataStore.start();. Even if I just observe, and don't run start, I get no events.

@manueliglesias DataStore version: 2.2.3-unstable.1

@mdoesburg The publish to npm just finished, try again, these are the versions:

Successfully published:

 - [email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - [email protected]+b5347ab62
 - [email protected]+b5347ab62
 - [email protected]+b5347ab62
 - [email protected]+b5347ab62
 - [email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62
 - @aws-amplify/[email protected]+b5347ab62

@manueliglesias Thank you, it works now. Keep up the great work.

My use case for data sync notifications is so I can warn users before logging out if the data they created while offline has not yet been synced with the cloud. Is there a chance that use cases like this will be added to the Amplify documentation? How can I check if theres still items that were created offline that haven't been synced? Like which event do I have to listen to on the Hub?

@manueliglesias Nevermind I figured out I can listen to the outboxStatus event:
outboxStatus {"isEmpty": false}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

callmekatootie picture callmekatootie  路  3Comments

shinnapatthesix picture shinnapatthesix  路  3Comments

rayhaanq picture rayhaanq  路  3Comments

ddemoll picture ddemoll  路  3Comments

guanzo picture guanzo  路  3Comments