Updating a secondary table using the id of a create

I have a separate table that keeps track of item ordering, so when a new item is created, I need to update the order array (a allow multiple ID field) in another table once I know the id. I’m running into some race conditions in the UI trying to do it sequentially, meaning after use a mutation to create the new item, I then call another mutation to update the order.

One option of course is to use a custom function or an after create trigger. I believe doing it there will be more transactional.

Are there any other options such as creating transactions another way, or is there an append operation for “allow multiple” fields, for example? Or is there any way to pass the id of the newly created item to an update mutation within the same mutation group?

I could create a separate table to store the ordering, making it easier to append item ids, but “allow multiple” fields are very handy for re-arranging values so I’d like to stick with that if possible.

Any suggestions appreciated.

Mike

Update: Ultimately, I created a queue to ensure that first, requests were guaranteed to be in order, and second, all previous requests were finished and the cache was updated before processing the next item in the queue.

It takes 1-2 seconds for a new item to show up, so to additionally I want to add a “in progress” state so the user sees the item added immediately. Apollo GraphQL does support an “optimisticResponse” option, but because there are multiple tabled updating it’s more difficult to coordinate.

(Typescript type annotations take out for brevity)

  const page = useQuery(GET_ITEMS);

  const [createItem] = useMutation(CREATE_ITEM, {
    update(cache, { data: { itemCreate } }) {
      cache.modify({
        fields: {
          itemsList(existingItemsList = { existingItemsList: { items: [] } }) {
            const newItemRef = cache.writeFragment({
              data: itemCreate,
              fragment: ITEM_FRAGMENT,
            });

            return {
              ...existingItemsList,
              items: [...existingItemsList.items, newItemRef],
            };
          },
        }
      });
    },
  });

  const [updateSortOrder] = useMutation(UPDATE_ITEMS_ORDER, {
    fetchPolicy: 'no-cache', // Don't need to cache since we're updating the cache directly
    update(cache, { data: { sortingOrderUpdate } }) {
      cache.modify({
        fields: {
          sortingOrder(existingSortingOrder = { order: [] }) {
            const newTodoRef = cache.writeFragment({
              data: sortingOrderUpdate,
              fragment: gql`
                fragment SortingOrderFragment on SortingOrder {
                  id
                  order
                }
              `
            });

            return {
              ...existingSortingOrder,
              order: newTodoRef,
            };
          }
        }
      });
    }
  });

  const processCreateItemQueue = useCallback(async () => {
    if (isQueueProcessing.current) {
      return;
    }

    isQueueProcessing.current = true;

    while (createItemQueue.current?.length > 0) {
      const options = createItemQueue.current.shift();

      const data = await createItem(options);

      // Make sure we have the latest cached value
      const sortingOrder = client.readFragment({
        id: 'SortingOrder:clsf5se3o01js0ajwbt0dcfzg',
        fragment: gql`
          fragment SortingOrderFragment on SortingOrder {
            id
            order
          }
        `
      });

      await updateSortOrder({
        variables: {
          order: [...sortingOrder.order, data.data.itemCreate.id]
        }
      });
    }

    isQueueProcessing.current = false;
  }, [client, createItem, updateSortOrder]);

  const queueCreateItem = useCallback((options) => {
    createItemQueue.current?.push(options);

    processCreateItemQueue();
  }, [processCreateItemQueue]);