DEV Community

Cover image for React Query as a persistent state manager
Chris Bongers
Chris Bongers

Posted on • Originally published at daily-dev-tips.com

React Query as a persistent state manager

I had to leverage a kind of context-based state a while ago; however, the element needing context was so small that it seemed overkill to make a full context for this.

And that's when I started building this context of a small reusable hook that does just that.

To demonstrate the difference and difficulties with managing a persistent shareable state, I will also demo another option and work our way up to modify it by leveraging React Query.

Below you can see a short video demo to showcase the downsides of the persistent state hook compared to the React Query hook.

React query as a persistent state manager

A persistent state hook in React

Let's start by creating a persistent state hook in React.
This will be a hook that we can use to read and write from a specified storage module.
I'll use local storage in this example, but you can change this to any storage.

The hook should be able to retrieve the data set in the storage module and return that.
In return, it should be able to persist a new value in the storage, and the module should return this.

Let's create a file called usePersistentState.
The hook will look like this:

import {useState, useEffect} from 'react';

export default function usePersistentState(key) {
  const [value, setValue] = useState(null);

  const setValueAndPersist = (newValue) => {
    if (newValue !== value) {
      setValue(newValue);
      return localStorage.setItem(key, newValue);
    }
    return value;
  };

  useEffect(() => {
    const item = localStorage.getItem(key);
    if (item) {
      setValue(item);
    }
  }, []);

  return [value, setValueAndPersist];
}
Enter fullscreen mode Exit fullscreen mode

We leverage a react useState hook to keep track of the value.
And we use the useEffect hook to run once it mounts by using the [] property.

By using [] will only run on load.

To use this hook we can do something like this:

function SetState() {
  const [value, setValue] = usePersistentState('item_state');
  return (
    <button onClick={() => setValue(value === 'on' ? 'off' : 'on')}>
      Click me {value}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

And this will work perfectly.

Until... We need to introduce another component that also needs to read this value separately.
Since we used useState it does not update across our application, and it will cause really weird side-effects.

React Query as a state manager

You might have remembered that React Query does not have to work with API calls. It can keep track of any variable.

And in our case, we want it to keep track of our storage object.
So let's also create a usePeristentContext hook.

This will be our hook that uses React Query to keep track of our state.

import {useMutation, useQuery, useQueryClient} from 'react-query';

export default function usePersistentContext(key) {
  const queryClient = useQueryClient();

  const {data} = useQuery(key, () => localStorage.getItem(key));

  const {mutateAsync: setValue} = useMutation(
    (value) => localStorage.setItem(key, value),
    {
      onMutate: (mutatedData) => {
        const current = data;
        queryClient.setQueryData(key, mutatedData);
        return current;
      },
      onError: (_, __, rollback) => {
        queryClient.setQueryData(key, rollback);
      },
    }
  );

  return [data, setValue];
}
Enter fullscreen mode Exit fullscreen mode

You can see that we define the query to read from the localStorage. This will be able to set our initial value if it exists.

Then we use a React Query mutation as our set value. This can update our storage and, in the meantime, mutate the query data so it will reflect application-wide!

We can use this hook in the following fashion:

function SetContext() {
  const [value, setValue] = usePersistentContext('item_context');
  return (
    <button onClick={() => setValue(value === 'on' ? 'off' : 'on')}>
      Click me {value}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

The benefit of this method is that another component can read it simultaneously, and the updated value will be read!

Viva la React Query!

You can try both methods on this Code Sandbox.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Top comments (0)