Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Referencing Deeper Object Data for Parent Objects in Amplify Gen 2 #13363

Closed
3 tasks done
ChristopherGabba opened this issue May 11, 2024 · 6 comments
Closed
3 tasks done
Assignees
Labels
Gen 2 Issues related to Gen 2 Amplify projects GraphQL Related to GraphQL API issues question General question

Comments

@ChristopherGabba
Copy link

ChristopherGabba commented May 11, 2024

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

GraphQL API, DataStore

Amplify Version

v6

Amplify Categories

api

Backend

Amplify Gen 2 (Preview)

Environment information

  System:
    OS: macOS 14.4.1
    CPU: (10) arm64 Apple M2 Pro
    Memory: 881.88 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.7.0 - /opt/homebrew/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 10.1.0 - /opt/homebrew/bin/npm
    Watchman: 2023.09.04.00 - /opt/homebrew/bin/watchman
  Browsers:
    Safari: 17.4.1
  npmPackages:
    %name%:  0.1.0 
    @aws-amplify/backend: ^1.0.1 => 1.0.1 
    @aws-amplify/backend-cli: ^1.0.2 => 1.0.2 
    @aws-amplify/react-native: ^1.1.0 => 1.1.0 
    @aws-amplify/ui-react-native: ^2.2.0 => 2.2.0 
    @babel/core: ^7.20.0 => 7.24.5 
    @babel/plugin-proposal-export-namespace-from: ^7.18.9 => 7.18.9 
    @babel/plugin-proposal-optional-chaining: ^7.0.0 => 7.21.0 
    @babel/plugin-transform-arrow-functions: ^7.0.0 => 7.24.1 
    @babel/plugin-transform-nullish-coalescing-operator: ^7.0.0 => 7.24.1 
    @babel/plugin-transform-shorthand-properties: ^7.0.0 => 7.24.1 
    @babel/plugin-transform-template-literals: ^7.0.0 => 7.24.1 
    @babel/preset-env: ^7.20.0 => 7.24.5 
    @babel/runtime: ^7.20.0 => 7.24.5 
    @config-plugins/ffmpeg-kit-react-native: ^8.0.0 => 8.0.0 
    @expo-google-fonts/m-plus-1p: ^0.2.3 => 0.2.3 
    @expo-google-fonts/montserrat: ^0.2.3 => 0.2.3 
    @expo/config-plugins: ~8.0.0 => 8.0.4 (7.9.2)
    @expo/metro-runtime: ~3.1.3 => 3.1.3 
    @gorhom/bottom-sheet: ^4.6.1 => 4.6.1 
    @react-native-async-storage/async-storage: ^1.23.1 => 1.23.1 
    @react-native-community/netinfo: ^11.3.1 => 11.3.1 
    @react-navigation/bottom-tabs: ^6.5.20 => 6.5.20 
    @react-navigation/native: ^6.0.2 => 6.1.17 
    @react-navigation/native-stack: ^6.0.2 => 6.9.26 
    @sentry/react-native: ~5.22.0 => 5.22.2 
    @shopify/flash-list: 1.6.4 => 1.6.4 
    @types/i18n-js: 3.8.2 => 3.8.2 
    @types/jest: ^29.2.1 => 29.5.12 
    @types/lodash.filter: ^4.6.9 => 4.6.9 
    @types/react: ~18.2.14 => 18.2.79 
    @types/react-test-renderer: ^18.0.0 => 18.3.0 
    @typescript-eslint/eslint-plugin: ^5.59.0 => 5.62.0 
    @typescript-eslint/parser: ^5.59.0 => 5.62.0 
    ContextAPIMixpanel:  0.0.1 
    HelloWorld:  0.0.1 
    MixpanelDemo:  0.0.1 
    SimpleMixpanel:  0.0.1 
    apisauce: 3.0.1 => 3.0.1 
    aws-amplify: ^6.3.0 => 6.3.0 
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/data:  undefined ()
    aws-amplify/data/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    aws-cdk: ^2.141.0 => 2.141.0 
    aws-cdk-lib: ^2.141.0 => 2.141.0 
    babel-jest: ^29.2.1 => 29.7.0 
    cheerio: ^1.0.0-rc.12 => 1.0.0-rc.12 
    constructs: ^10.3.0 => 10.3.0 
    date-fns: ^2.30.0 => 2.30.0 
    esbuild: ^0.21.1 => 0.21.1 (0.20.2)
    eslint: 8.17.0 => 8.17.0 
    eslint-config-prettier: 8.5.0 => 8.5.0 
    eslint-config-standard: 17.0.0 => 17.0.0 
    eslint-plugin-import: 2.26.0 => 2.26.0 
    eslint-plugin-n: ^15.0.0 => 15.7.0 
    eslint-plugin-promise: 6.0.0 => 6.0.0 
    eslint-plugin-react: 7.30.0 => 7.30.0 
    eslint-plugin-react-native: 4.0.0 => 4.0.0 
    eslint-plugin-reactotron: ^0.1.2 => 0.1.4 
    ex: ^0.1.4 => 0.1.4 
    expo: ^51.0.2 => 51.0.2 
    expo-application: ~5.9.1 => 5.9.1 
    expo-av: ~14.0.4 => 14.0.4 
    expo-blur: ~13.0.2 => 13.0.2 
    expo-build-properties: ^0.12.1 => 0.12.1 
    expo-clipboard: ~6.0.3 => 6.0.3 
    expo-constants: ^16.0.1 => 16.0.1 
    expo-contacts: ~13.0.3 => 13.0.3 
    expo-dev-client: ~4.0.13 => 4.0.13 
    expo-device: ~6.0.2 => 6.0.2 
    expo-font: ~12.0.4 => 12.0.4 
    expo-haptics: ~13.0.1 => 13.0.1 
    expo-image-picker: ~15.0.4 => 15.0.4 
    expo-linear-gradient: ~13.0.2 => 13.0.2 
    expo-linking: ~6.3.1 => 6.3.1 
    expo-localization: ~15.0.3 => 15.0.3 
    expo-modules-autolinking: ^1.11.1 => 1.11.1 
    expo-notifications: ^0.28.1 => 0.28.1 
    expo-secure-store: ~13.0.1 => 13.0.1 
    expo-share-intent: ^2.0.0 => 2.0.0 
    expo-splash-screen: ^0.27.4 => 0.27.4 
    expo-status-bar: ~1.12.1 => 1.12.1 
    expo-store-review: ~7.0.2 => 7.0.2 
    expo-updates: ^0.25.11 => 0.25.11 
    expo-video-thumbnails: ~8.0.0 => 8.0.0 
    ffmpeg-kit-react-native: ^6.0.2 => 6.0.2 
    i18n-js: 3.9.2 => 3.9.2 
    jest: ^29.2.1 => 29.7.0 
    jest-expo: ~51.0.1 => 51.0.1 
    libphonenumber-js: ^1.11.1 => 1.11.1 (1.9.47)
    libphonenumber-js-core:  undefined (1.0.0)
    libphonenumber-js-max:  undefined (1.0.0)
    libphonenumber-js-min:  undefined (1.0.0)
    libphonenumber-js-mobile:  undefined (1.0.0)
    libphonenumber-js/build:  undefined ()
    libphonenumber-js/core:  undefined ()
    libphonenumber-js/max:  undefined ()
    libphonenumber-js/max/metadata:  undefined ()
    libphonenumber-js/min:  undefined ()
    libphonenumber-js/min/metadata:  undefined ()
    libphonenumber-js/mobile:  undefined ()
    libphonenumber-js/mobile/examples:  undefined ()
    libphonenumber-js/mobile/metadata:  undefined ()
    lodash: ^4.17.21 => 4.17.21 
    lodash.filter: ^4.6.0 => 4.6.0 
    lottie-react-native: 6.7.0 => 6.7.0 
    mixpanel-react-native: ^3.0.2 => 3.0.3 
    mixpanelexpo:  1.0.0 
    mobx: 6.10.2 => 6.10.2 
    mobx-react-lite: 4.0.5 => 4.0.5 
    mobx-state-tree: 5.3.0 => 5.3.0 
    patch-package: 6.4.7 => 6.4.7 
    postinstall-prepare: 1.0.1 => 1.0.1 
    prettier: 2.8.8 => 2.8.8 (2.3.2, 1.19.1)
    react: 18.2.0 => 18.2.0 
    react-dom: 18.2.0 => 18.2.0 
    react-native: 0.74.1 => 0.74.1 
    react-native-blurhash: ^2.0.2 => 2.0.2 
    react-native-compressor: ^1.8.24 => 1.8.24 
    react-native-context-menu-view: ^1.16.0 => 1.16.0 
    react-native-device-info: ^10.13.2 => 10.13.2 
    react-native-fs: ^2.20.0 => 2.20.0 
    react-native-gesture-handler: ~2.16.1 => 2.16.2 
    react-native-get-random-values: ^1.11.0 => 1.11.0 
    react-native-mime-types: ^2.5.0 => 2.5.0 
    react-native-mmkv: ^2.12.2 => 2.12.2 
    react-native-reanimated: ~3.10.1 => 3.10.1 
    react-native-safe-area-context: ^4.10.1 => 4.10.1 
    react-native-screens: 3.31.1 => 3.31.1 
    react-native-static-safe-area-insets: ^2.2.0 => 2.2.0 
    react-native-touchable-scale: ^2.2.0 => 2.2.0 
    react-native-url-polyfill: ^2.0.0 => 2.0.0 
    react-native-vision-camera: ^4.0.3 => 4.0.3 
    react-native-volume-manager: ^1.10.0 => 1.10.0 
    react-native-web: ~0.19.6 => 0.19.11 
    react-native-webview: 13.8.6 => 13.8.6 
    react-native-youtube-iframe: ^2.3.0 => 2.3.0 
    react-test-renderer: 18.2.0 => 18.2.0 
    reactotron-core-client: ^2.8.13 => 2.9.3 
    reactotron-mst: ^3.1.7 => 3.1.9 
    reactotron-react-js: ^3.3.11 => 3.3.14 
    reactotron-react-native: ^5.0.5 => 5.1.6 
    ts-jest: ^29.1.1 => 29.1.2 
    ts-node: ^10.9.2 => 10.9.2 
    tsx: ^4.9.4 => 4.10.0 
    typescript: ^5.4.5 => 5.4.5 (4.4.4, 4.9.5)
    uuid: ^9.0.1 => 9.0.1 (8.3.2, 3.3.2, 7.0.3)
  npmGlobalPackages:
    @aws-amplify/cli-internal: 12.12.0
    @aws-amplify/cli: 12.11.0
    @react-native-community/netinfo: 9.4.1
    eas-cli: 9.0.3
    expo-cli: 6.3.10
    firebase-tools: 11.24.1
    n: 9.1.0
    node-gyp: 10.0.1
    node: 20.6.0
    npm: 10.7.0
    pod-install: 0.2.0
    react-native-spinkit: 1.5.1
    typescript: 5.4.5
    yarn: 1.22.22


Describe the bug

I have set up a Gen 2 backend deployment following the steps in the docs and here is my data/resource.ts setup:

  User: a
    .model({
      id: a.id().required(),
      birthdate: a.string().required(),
      firstName: a.string().required(),
      lastName: a.string().required(),
      username: a.string().required(),
      phoneNumber: a.phone().required(),
      pushToken: a.string(),
      profileImage: a.url(),
      profileImageBlurhash: a.string(),
      searchTerm: a.string().required(),
      sentFriendships: a.hasMany("Friendship", "senderId"),
      receivedFriendships: a.hasMany("Friendship", "receiverId"),
    })
    .secondaryIndexes((index) => [
      index("phoneNumber").queryField("listUsersByPhoneNumber"),
      index("searchTerm").queryField("listUsersBySearchTerm").sortKeys(["id"]),
    ])
    .authorization((allow) => [allow.owner()]),

  Friendship: a
    .model({
      id: a.id().required(),
      receiverId: a.id().required(),
      receiver: a.belongsTo("User", "receiverId"),
      senderId: a.id().required(),
      sender: a.belongsTo("User", "senderId"),
      status: a.ref("FriendStatus").required(),
    })
    .authorization((allow) => [allow.publicApiKey()])
    .secondaryIndexes((index) => [
      index("senderId")
        .name("bySender")
        .sortKeys(["receiverId"])
        .queryField("listFriendshipsBySenderId"),
      index("receiverId")
        .name("byReceiver")
        .sortKeys(["senderId"])
        .queryField("listFriendshipsByReceiverId"),
    ]),

Within the app, I use my secondary index query like so:

      const response = await client.models.Friendship.listFriendshipsByReceiverId({
          receiverId: currentUser.userId,
      })

      // should return a friendship object

      const firstFriendshipReceiver = response.data[0].receiver
      console.log(firstFriendshipReceiver) // logs: [Function anonymous]

      const firstFriendshipReceiver = response.data[0].receiver() // added function parenthesis
      console.log(firstFriendshipReceiver) // logs {"_A": null, "_x": 0, "_y": 3, "_z": {"_A": null, "_x": 0, "_y": 0, "_z": null}}

Expected behavior

I want to be able to reference response.data[0].receiver.firstName but I can't reference any user properties from my friendship query.

Reproduction steps

  1. Use the data structure and code above to try to reference user data through the friendship query results.

Code Snippet

Refer to issue description above.

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

iPhone 12

Mobile Operating System

iOS 17.2.1

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

Using Expo 51

@ChristopherGabba ChristopherGabba added the pending-triage Issue is pending triage label May 11, 2024
@ChristopherGabba ChristopherGabba changed the title Referencing Relational Data Objects in Amplify Gen 2 Referencing Deeper Object Data for Parent Objects in Amplify Gen 2 May 11, 2024
@cwomack cwomack added Gen 2 Issues related to Gen 2 Amplify projects GraphQL Related to GraphQL API issues labels May 13, 2024
@chrisbonifacio chrisbonifacio self-assigned this May 13, 2024
@chrisbonifacio
Copy link
Contributor

chrisbonifacio commented May 13, 2024

Hi @ChristopherGabba can you try awaiting the receiver in your code? You may have to lazy load the relationship data.

Like so:

const firstFriendshipReceiver = await response.data[0].receiver();

Here are our docs on lazy loading hasMany relationships:

https://docs.amplify.aws/react/build-a-backend/data/data-modeling/relationships/#lazy-load-a-has-many-relationship

@chrisbonifacio chrisbonifacio added question General question pending-response Issue is pending response from the issue requestor and removed pending-triage Issue is pending triage labels May 13, 2024
@ChristopherGabba
Copy link
Author

ChristopherGabba commented May 13, 2024

@chrisbonifacio Interesting. This worked, but it still creates an issue for me. I basically fetch the friendship (with underlying user (receiver/sender) nested data) and I apply it straight into my local state.

  1. const results = await fetchFriendshipsAndUsers()
  2. setState(results)

Now I will have to:

  1. const response = fetchFriendships()
  2. For each friendship // fetchEachUserData() by lazy loading
  3. setFriendships(response)
  4. setUsers(users)

It would be painful and inefficient to have to create a whole other query when the user data should already be available in the same friendship api call. I noticed in the same link you provided there is an eagerly load, which is what I'm looking for I think. How can I define a selectionSet: { selectionSet: ["id", "receiver.", "sender."] }, so that the deeper nested data just comes through initially in the first api call?

I had this working fine in a Gen 1 without having to lazy load by modifying the query in one of the amplify files locally that was created with the amplify codegen command:
aws/graphql/queries.ts

export const friendshipsByReceiverId =
  /* GraphQL */ `query FriendshipsByReceiverId(
  $receiverId: ID!
  $sortDirection: ModelSortDirection
  $filter: ModelFriendshipFilterInput
  $limit: Int
  $nextToken: String
) {
  friendshipsByReceiverId(
    receiverId: $receiverId
    sortDirection: $sortDirection
    filter: $filter
    limit: $limit
    nextToken: $nextToken
  ) {
    items {
      id
      senderId
      sender { // defined deeper nested return data without lazy loading
        id
        profileImage
        profileImageBlurhash
        firstName
        lastName
        username
        birthdate
        phoneNumber
        searchTerm
        pushToken
        createdAt
        updatedAt
        __typename
      }
      receiverId
      receiver { // defined deeper nested return data without lazy loading
        id
        profileImage
        profileImageBlurhash
        firstName
        lastName
        username
        birthdate
        phoneNumber
        searchTerm
        pushToken
        createdAt
        updatedAt
        __typename
      }
      status
      createdAt
      updatedAt
      __typename
    }
    nextToken
    __typename
  }
}
` as GeneratedQuery<
    APITypes.FriendshipsByReceiverIdQueryVariables,
    APITypes.FriendshipsByReceiverIdQuery
  >;

Now this file is not exposed for editting in Gen 2.

I think this is super valuable, and I doubt I am the only user who will need this. Perhaps this can be a secondaryIndex input when defining a query?

@github-actions github-actions bot removed the pending-response Issue is pending response from the issue requestor label May 13, 2024
@chrisbonifacio
Copy link
Contributor

chrisbonifacio commented May 14, 2024

Hey @ChristopherGabba you can do "receiver.*" in the selection to select and eagerly load all fields on a nested/related record.

@ChristopherGabba
Copy link
Author

ChristopherGabba commented May 14, 2024

Thanks @chrisbonifacio thats exactly what I was looking for!
I couldn't get the selectionSet field to show up but it's actually a second object parameter input into the list query.

Is there a way to define this globally instead of in every query?

Example:

receiver: a.belongsTo("User", "receiverId").includeInSelectionSet()

The problem with it having to be defined in every query is that now if I try to reference a friendship type:

const friendship: Schema["Friendship"]["type"] = ...

const firstName = friendship.reciever.firstName // throws typescript error on firstname because the main schema won't allow me to reference the nested object data even when I know it is there.

Defining the selection sets seems to override the tyepscript for the results of the query but I want that to be global so throughout my app, I can reference things deeper without typescript errors.

@chrisbonifacio
Copy link
Contributor

@ChristopherGabba the solution here might be to define the selection set separately and combine its typing with Schema using our SelectionSet helper type:

Here's an example:

import type { SelectionSet } from 'aws-amplify/data';
import type { Schema } from '../amplify/data/resource';


const selectionSet = ['content', 'blog.author.*', 'comments.*'] as const;
type PostWithComments = SelectionSet<Schema['Post']['type'], typeof selectionSet>;

// ...
const [posts, setPosts] = useState<PostWithComments[]>([]);

const fetchPosts = async () => {
  const { data: postsWithComments } = await client.models.Post.list({
    selectionSet,
  });
  setPosts(postsWithComments);
}

https://docs.amplify.aws/react/build-a-backend/data/query-data/#typescript-type-helpers-for-amplify-data

@ChristopherGabba
Copy link
Author

@chrisbonifacio Awesome! This worked. Closing this issue as complete, thanks again for all the help, I know that Gen 2 is fairly new.

I will propose a feature request in a separate thread like so:

receiver: a.belongsTo("User", "receiverId").includeInSelectionSet()

So that custom types won't have to be defined, and you won't have to set selectionSets separately on each query, should the user not want it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Gen 2 Issues related to Gen 2 Amplify projects GraphQL Related to GraphQL API issues question General question
Projects
None yet
Development

No branches or pull requests

3 participants