0

Calling setQueryData on a socket event listener does not rerender components.

I have a React project that uses react-query to manage query related logic. I am also using socket.io rooms for data updates. The client may subscribe to a room and receive update payloads for that specific resource.

socket.on("DATA:consumable", (payload: { room: ""; data: unknown }) => {
  const room = payload.room.split(":"); // room: "type:id"
  const resourceType = room[0];
  const resourceId = room[1];

  queryClient.setQueryData([resourceType, resourceId], (old) => { 
    // old is not touched
    return payload.data;
  });
});

In this event listener, I retrieve the resourceType and resourceId, call setQueryData with the appropriate query key, and return the payload data. I am not mutating the old state of this query.

const Todo = ({ data }: { data: Todo }) => {

  const todo = queryClient.getQueryData<Todo>(["todo", data._id]) || data;

  return (
    ...
  );
};

const Todos = ({ todos }: { todos: Todo[] }) => {
  return (
    <>
      {todos.map((todo) => (
        <Todo key={todo._id} data={todo} />
      ))}
    </>
  );
};

In these two component, I define todo as the react-query state (if defined) or the data provided by the todos component. When the data is updated in the above socket listener, I expect the cache to be set and the Todo component to reflect the updated state. In the react-query devtools I observe that the cache is properly set when new data is emitted.

Replacing react-query cache with other stores correctly rerenders the Todo component.

With redux:

socket.on("DATA:consumable", (payload: { room: ""; data: unknown }) => {
  ...

  dispatch(setTestTodo(payload.data));
});
const Todo = ({ data }: { data: Todo }) => {

  const testTodoState = useSelector((state) => state.testTodo);

  const todo = testTodoState || data;

  return (
    ...
  );
};

Here the todo component correctly reflects state updates. Other questions about setQueryData not rerendering components are resolved by cloning the old cache in the updater callback rather than mutating the old cache state. I am updating my cache state immutably so this shouldn't be an issue.

2
  • 3
    I don't think you can use getQueryData function inside of a component, it's not reactive etc. You need to use useQuery hook instead. Commented Jul 4, 2023 at 12:01
  • ⬆️ this is the correct answer. Please put it into an answer (instead of a comment) so that we can accept it. Commented Jul 17, 2023 at 13:45

1 Answer 1

2

When using queryClient.getQueryData() to fetch data from the React Query cache, it won't initiate a re-render when the query's data changes. It's sort of a one-way ticket. React Query uses the React-based subscription model to re-render components when their data updates, and this subscription is created when using hooks e.g. useQuery(), useMutation(), etc.

How does the subscription-model work?

Let's look at an analogy of how the subscription model works:

Say you're a customer (component) and you're interested in some newspaper (React). You decide to subscribe to the company and you tell them what kind of news (data) you're interested in. In React terms, this is like when a component mounts and specifies the state it needs to render properly e.g by using hooks like useState, useEffect, useQuery, etc.

The Newspaper company (React) keeps track of all its customers (components) and what kind of news (data) they're interested in. It does this by creating a subscription for each customer. This is the "subscription model" - each component (customer) subscribes to the specific state (news) it's interested in.

When some news (state/data) updates, the newspaper (React) doesn't just print the new newspaper and send it to every single customer. It first looks at its list of subscriptions, and only sends the updated news to customers who have subscribed to that type of news.


So given that, queryClient.getQueryData() is more of an "imperative" dry way of getting data, meaning that you will simply grab the current state of a query at the moment it's called and won't have this subscription so it won't re-render with new data because it doesn't know there is any.

As suggested in one of the comments, you can use useQuery to fetch the data. This way, your components will re-render as expected whenever the data changes:


import { useQuery } from '@tanstack/react-query';

const Todo = ({ id }: { id: string }) => {
  const { data: todo, isLoading } = useQuery<Todo, Error>(['todo', id]);

  // ... and so on

};

This will make your todo components subscribe to its data through useQuery() so when the cache updates in React Query, your components will also update.

TLDR;

Use hooks to subscribe for data changes in react components. This will cause them to re-render when React Query cache updates.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.