Search code examples
javascriptreactjsreact-query

React-query setQueryData not re-rendering component


I've been dealing for a while with this problem and still can't tackle it.

I'm using React-query as a server state management library and I'm trying to get my UI state synchronized with my server state when a mutations occurs. Since I can use the mutation response to avoid a new API call, I'm using the setQueryData feature that React-query gives us.

The problem is that the old-data is being correctly modified (I can see it in the react-query DevTools) when a mutation is successful, but the component using it isn't being re-rendered, making my UI State not synchronized with my Server state (well, at least the user can't see the update).

Let me show some code and hope someone can give me some insights.

Component using the query:

const Detail = ({ orderId }) => {
  const { workGroups } = useWorkGroups();
  const navigate = useNavigate();

  const queryClient = useQueryClient();
  const orderQueries = queryClient.getQueryData(["orders"]);
  const queryOrder = orderQueries?.find((ord) => ord.id === orderId);

// more code

Component mutating the query:

const Deliver = ({
  setIsModalOpened,
  artisan,
  index,
  queryQuantity,
  queryOrder,
}) => {
  const [quantity, setQuantity] = useState(() => queryQuantity);

  const { mutate: confirmOrderDelivered } = useMutateOrderDeliveredByArtisan(
    queryOrder.id
  );

  const onSubmit = () => {
    confirmOrderDelivered(
      {
        id: queryOrder.artisan_production_orders[index].id,
        artisan: artisan.user,
        items: [
          {
            quantity_delivered: quantity,
          },
        ],
      },
      {
        onSuccess: setIsModalOpened(false),
      }
    );
  };

// more code

Now the mutation function (ik it's a lot of logic but I dont' want to refetch the data using invalidateQueries since we're dealing with users with a really bad internet connection). Ofc you don't need to understand each step of the fn but what it basically does is update the old queried data. In the beginning I thought it was a mutation reference problem since React using a strict comparison under the hood but I also checked it and It doesn't look like it's the problem. :

{
      onSuccess: (data) => {
        queryClient.setQueryData(["orders"], (oldQueryData) => {
          let oldQueryDataCopy = [...oldQueryData];
          const index = oldQueryDataCopy.findIndex(
            (oldData) => oldData.id === orderId
          );

          let artisanProdOrders =
            oldQueryDataCopy[index].artisan_production_orders;

          let artisanProductionOrderIdx = artisanProdOrders.findIndex(
            (artProdOrd) => artProdOrd.id === data.id
          );

          artisanProdOrders[artisanProductionOrderIdx] = {
            ...artisanProdOrders[artisanProductionOrderIdx],
            items: data.items,
          };

          const totalDelivered = artisanProdOrders.reduce((acc, el) => {
            const delivered = el.items[0].quantity_delivered;
            return acc + delivered;
          }, 0);

          oldQueryDataCopy[index] = {
            ...oldQueryDataCopy[index],
            artisan_production_orders: artisanProdOrders,
            items: [
              {
                ...oldQueryDataCopy[index].items[0],
                quantity_delivered: totalDelivered,
              },
            ],
          };
          return oldQueryDataCopy;
        });
      },

      onError: (err) => {
        throw new Error(err);
      },
    }

And last but not least: I already checked that the oldQueryData is being correctly modified (console loging in the onSuccess fn in the mutation response) and, as I said before, the data is correctly modified in the React-query DevTools.

I know this is a lot of code and the problem seems to be complex but I really believe that it might be a really easy thing that I'm not pointing out because of how tired I already am.

Thanks!


Solution

  • Well, I fixed it in the worst possible way imho, so I will answer this question but I really would like to read your thoughts.

    It looks like the new query data setted on the expected query is re-rendering the component only if the mutation function is located in the component that we actually want to re-render.

    With that in mind what I did was just colocate my mutation function in the parent component and pass it down through the child component.

    Something like this:

    const Detail = ({ orderId }) => {
      const { workGroups } = useWorkGroups();
      const navigate = useNavigate();
    
    const { mutate: confirmOrderDelivered } = useMutateOrderDeliveredByArtisan(
        queryOrder.id
      ); ==============> THE MUTATION FN IS NOW IN THE PARENT COMPONENT
    
      const queryClient = useQueryClient();
      const orderQueries = queryClient.getQueryData(["orders"]);
      const queryOrder = orderQueries?.find((ord) => ord.id === orderId);
    
    // more code
    

    First child:

    const Assigned = ({ artisan, queryOrder, index, confirmOrderDelivered }) => {
    
    // THE IMPORTANT PART HERE IS THE PROP BEING PASSED DOWN.
    
     <Modal
              isOpen={isModalOpened}
              toggleModal={setIsModalOpened}
              // className="w312"
              width="80%"
              height="fit-content"
              justifyCont="unset"
              alignItems="unset"
            >
              <Deliver
                setIsModalOpened={setIsModalOpened}
                artisan={artisan}
                queryQuantity={quantity}
                queryOrder={queryOrder}
                index={index}
                confirmOrderDelivered={confirmOrderDelivered} => HERE
              />
            </Modal>
    

    Component that actually needs the mutation fn:

    const Deliver = ({
      setIsModalOpened,
      artisan,
      index,
      queryQuantity,
      queryOrder,
      confirmOrderDelivered,
    }) => {
      const [quantity, setQuantity] = useState(() => queryQuantity);
    
      const onSubmit = () => {
        confirmOrderDelivered( => HERE.
          {
            id: queryOrder.artisan_production_orders[index].id,
            artisan: artisan.user,
            items: [
              {
                quantity_delivered: quantity,
              },
            ],
          }
        );
      };