Looking for the right way to create and update item with GraphQL in Amplify / React

833 Views Asked by At

I'm new to Amplify, React, and GraphQL. I'm looking to figure out the right way to make a mutation that has an @hasMany relationship between tables (for context, I am using a multi-table solution since that seems to work best with Amplify).

I'm trying to build an app that has a user, a couple of children for each user, and stories for each child... and I'm completely stumped on how to make this @hasMany connection between multiple tables for both create and update.

Here's my schema.graphql:

type User @model @auth(rules: [{allow: owner}]) {
  id: ID!
  email: String! @index(name: "byEmail", queryField: "userByEmail", sortKeyFields: ["id"])
  children: [Child] @hasMany
  settings: String
  lastInteraction: String
}

type Child @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String!
  age: String!
  commonNoun: String!
  user: User @belongsTo
  stories: [Story] @hasMany
}

type Story @model @auth(rules: [{allow: owner}]) {
  id: ID!
  child: Child @belongsTo
  status: String
  storyType: String
  storyData: String!
}
 

I've been able to successfully create a user and a child, but now I want to link the two. Here's my code so far for creating a User:

export async function createNewUserinDB(userEmail) {
      
  const data = {
    email: userEmail
  };

  const userCreationResponse = await API.graphql({
    query: createUser,
    variables: { input: data },
    authMode: "AMAZON_COGNITO_USER_POOLS"
  }).then(response => {
    console.log(response);
    return response;
  }).catch(e => {
    console.log("catch");
      console.log(e);
      return null;
  });

  return(userCreationResponse);

}

And for creating a Child:

async function createChildinDB(childAge, childName, childCommonNoun, userID) {
      
    const data = {
      age: childAge,
      name: childName,
      commonNoun: childCommonNoun,
    };

    const childCreationResponse = await API.graphql({
      query: createChild,
      variables: { input: data },
      authMode: "AMAZON_COGNITO_USER_POOLS"
    }).then(response => {
      console.log(response);
      return response;
    }).catch(e => {
        console.log(e);
        return null;
    });

    return(childCreationResponse);

  }

I've tried creating a separate function to pass the already created Child into a UserUpdate, and I can't seem to get it right. I get this error when I execute the following code: "The variables input contains a field name 'children' that is not defined for input object type 'UpdateUserInput' "

export async function addChildtoUserInDB(userID, userEmail, childData) {
      
  const data = {
    id: userID,
    email: "Sparky", // this was for testing; works fine if this line exists but no line below
    children: [childData]
  };

  console.log(childData);

  const updateUserResponse = await API.graphql({
    query: updateUser,
    variables: { input: data },
    authMode: "AMAZON_COGNITO_USER_POOLS"
  }).then(response => {
    console.log(response);
    return response;
  }).catch(e => {
    console.log("catch");
      console.log(e);
      return null;
  });

  return(updateUserResponse);

}

The code works just fine if I comment out the line about 'children'. I've tried all sorts of different things; e.g. dropping the brackets and different formats, changed it to Child, different versions of this String solution, I also found this input type thing, but it felt like it wouldn't be compatible with multiple @hasMany table references.

I also found this solution of creating a child/leaf item while referencing the parent, but wasn't sure how to adapt it, since the syntax in the last code example "userToDoItemsOwner" seemed to come out of no where.

Would welcome any thoughts here; I'm feeling so stuck! :)

1

There are 1 best solutions below

1
femmedecentral On

I figured it out! I ended up modifying my schema a little to the following, dropping the @belongsTo per Luke's suggestion:

type User @model @auth(rules: [{allow: owner}]) {
  id: ID!
  email: String! @index(name: "byEmail", queryField: "userByEmail", sortKeyFields: ["id"])
  children: [Child] @hasMany
  settings: String
  lastInteraction: String
}

type Child @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String!
  age: String!
  commonNoun: String!
  stories: [Story] @hasMany
}

type Story @model @auth(rules: [{allow: owner}]) {
  id: ID!
  status: String
  storyType: String
  storyData: String!
}

The correct pattern was to create the Child item while referencing the userID using "userChildrenId". This is what the creation code for a child object associated with the owning User item:

  async function createChildinDB(childAge, childName, childCommonNoun, userID) {
      
    const data = {
      age: childAge,
      name: childName,
      commonNoun: childCommonNoun,
      userChildrenId: userID
    };

    const childCreationResponse = await API.graphql({
      query: createChild,
      variables: { input: data },
      authMode: "AMAZON_COGNITO_USER_POOLS"
    }).then(response => {
      return response;
    }).catch(e => {
        console.log(e);
        return null;
    });

    return(childCreationResponse);

  }

With this pattern, I was also still able to successfully query for all children associated with a particular user:

export async function getChildrenforUser(userID) {

  // filter on only children of user
  const variablesForChildrenofUserQuery = {
    filter: {
      userChildrenId: {
        eq: userID
      }
    }

  }

  const getUserChildren = await API.graphql({
    query: listChildren,
    variables: variablesForChildrenofUserQuery,
    authMode: "AMAZON_COGNITO_USER_POOLS"
  }).then(response => {
    console.log(response);
    return response;
  }).catch(e => {
      console.log(e);
      return null;
  });

  return(getUserChildren);
}

Hope this helps someone else in the future :)