Managing Global State with React Query

Managing Global State with React Query

Intro

React Query is a great data-fetching library for React. Typically, it's employed for managing API interactions and server state.

However, this article explores an innovative approach: harnessing React Query's cache as an alternative method for sharing state among your components.

Yes, you might be thinking, "Isn't that what context and Redux are for?" Hold that thought for a moment; we're about to delve into a cool alternative that has proven helpful for me more than once.

Let’s use the good old “counter” (raise your 🖐🏻 if you hate counter-examples) to illustrate how this works. First, let’s create a SetCount component whose only job will be to write to the cache.

const SetCount = () => {
  const initialData = 0;
  const { data: value } = useQuery("shared-count", () => initialData, {
    enabled: false,
    initialData,
  });

  return (
    <>
      <button onClick={() => client.setQueryData("shared-count", value + 1)}>
        +
      </button>
      <button onClick={() => client.setQueryData("shared-count", value - 1)}>
        -
      </button>
    </>
  );
};

Let me provide some additional details on how this works. We're utilizing "shared-count" as the key or identifier within the cache. Since we're not making any asynchronous requests, we simply provide a function that returns our initial state, like this: () => 0.

When configuring the options for the useQuery hook, we set the enable property to false to prevent this query from running automatically (as there's no actual request involved). Finally, we supply the initialData.

To access and display this state, we'll create a view component. This view consists of a simple paragraph that renders whatever React Query's cache returns for our key, which in this case is 'shared-count':

const Count = () => {
  const { data: value } = useQuery("shared-count", {
    enabled: false,
  });

  return <p>{value}</p>;
};

Now you should automatically see the new count displayed in the UI every time that we click any button from the SetCount component.

Improvement

Now let’s do what every dev loves doing… let’s optimize early! Just kidding, but we can still do better, right?

Let’s make this more reusable by creating a hook that provides this functionality every time that you need it.

const useGlobalState = (key, initialData) => [
  useQuery(key, () => initialData, {
    enabled: false,
    initialData,
  }).data,
  // setter function
  value => client.setQueryData(key, value)
];

Now let’s refactor your components to use the new hook:

const SetCount = () => {
  const [value, setValue] = useGlobalState("count", 0);

  return (
    <>
      <button onClick={() => setValue(value + 1)}>+</button>
      <button onClick={() => setValue(value - 1)}>-</button>
    </>
  );
};

const Count = () => {
  const [value] = useGlobalState("count");

  return <p>{value}</p>;
};

You can find all the code and a working demo in codesanbox.

Conclusion

Does this replace a global state management solution? Absolutely not!

Should I do this instead of using Context? Nope!

So why the heck are you teaching me this?

If you're already using React Query, this offers a neat alternative for sharing small pieces of data among your components, especially when the task of implementing a more robust solution isn't warranted or ideal.

I've personally employed this approach in real-world production applications. It's proven to be quite handy for patching or swiftly addressing issues in existing features where a complete refactor to use context or a global state solution wasn't feasible.

And I repeat (cause I know people will comment about this), this is definitely not a replacement for a global state management library or for React context.

Hope you enjoyed learning about how to use the React Query cache as an alternative for global state 🙂, until the next post 👋🏻.