'How to configure custom key for data that has composite primary key in @urql/exchange-graphcache cacheExchange

On the frontend I have a Cart, it has buttons to call a GraphQL mutation to increase or decrease the quantity of a product. The mutation worked as expected, except for one thing is that the quantity number on the frontend doesn't change until I reload the page. I know the problem is at the cache and I need to somehow update the cache when I click the button and call the mutation.

On the backend I use TypeGraphQL together with TypeORM, on the frontend I use urql, with @urql/exchange-graphcache to configure caching. Here is my attempt on updating the cache:

const client = createClient({
  url: "http://localhost:4000/graphql",
  fetchOptions: {
    credentials: "include" as const,
  },
  exchanges: [
    dedupExchange,
    cacheExchange({
      updates: {
        Mutation: {
          qtyCart: (_result, args, cache, info) => {
            const { productId, type } = args as QtyCartMutationVariables;
            const data = cache.readFragment(
              gql`
                fragment _ on Cart {
                  productId
                  userId
                  qty
                }
              `,
              { productId }
            );
            if (data) {
              let newQty = 0;
              if (type === "inc") {
                newQty = (data.qty as number) + 1;
              } else if (type === "dec") {
                newQty = (data.qty as number) - 1;
              }

              cache.writeFragment(
                gql`
                  fragment __ on Cart {
                    qty
                  }
                `,
                { productId, qty: newQty }
              );
            }
          },
        },
      },
    }),
    fetchExchange,
  ],
});

Also here are the mutation and the Cart entity:

  • Mutation handle quantity update:

    @Mutation(() => Cart)
    async qtyCart(
      @Arg("productId", () => Int) productId: number,
      @Arg("type") type: "inc" | "dec",
      @Ctx() { req }: MyContext
    ): Promise<Cart | null> {
      const userId = req.session.userId;
    
      if (!userId) {
        return null;
      }
    
      const cart = await Cart.findOne({ where: { userId, productId } });
    
      if (!cart) {
        return null;
      }
    
      const product = await Product.findOne({ where: { id: productId } });
      const stock = product!.countInStock;
    
      if (type === "inc" && cart.qty < stock) {
        await Cart.update({ userId, productId }, { qty: cart.qty + 1 });
      } else if (type === "dec" && cart.qty > 1) {
        await Cart.update({ userId, productId }, { qty: cart.qty - 1 });
      }
    
      return cart;
    }
    
  • Cart entity:

    @ObjectType()
    @Entity()
    export class Cart extends BaseEntity {
      @Field()
      @PrimaryColumn()
      userId!: number;
    
      @Field(() => User)
      @ManyToOne(() => User, (user) => user.items)
      user: User;
    
      @Field()
      @PrimaryColumn()
      productId!: number;
    
      @Field(() => Product)
      @ManyToOne(() => Product, (product) => product.cart)
      product: Product;
    
      @Field(() => Int)
      @Column()
      qty!: number;
    }

On my attempt I do spot an error which is mentioned on urql/graphcache documentation, which is the invalid key error. Then I tried to figure out how to create a custom keys config, but still don't know how to do it right, since my Cart entity has a composite primary key, which the documentation doesn't mention.

Thanks in advance for the help. If you need further details then I am pleased to provide.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source