Search code examples
javascriptreactjssocketsreduxrtk-query

How to invalidate all Redux RTK queries when special event dispatched (socket IO message)?


I want to synchronize SPA react application for all users (several tabs, several users). I wrote a simple react hook which listens for specific socket IO message and updates state variable upon a message called useGetAllEmployeesQuery:

import { useEffect, useState } from 'react'

export function useRefetchOnMessage (messageType) {
  const [needRefetch, setNeedRefetch] = useState()

  if (!window.io.socket) {
    return
  }

  function handleMessage (e) {
    setNeedRefetch(Date.now())
  }

  useEffect(() => {
    window.io.socket.on(messageType, handleMessage)

    return () => {
      window.io.socket.off(messageType, handleMessage)
    }
  }, [])

  return needRefetch
}

When this event is dispatched I simply call RTK refetch() method.

const employees = useGetAllEmployeesQuery(officeId)

const needRefreshEmployees = useRefetchOnMessage('employees changed')

useEffect(() => {
  if (!needRefreshEmployees) return

  employees.refetch()
}, [needRefreshEmployees])

As a result we need to call it everywhere in application, imagine we have 5 components using employees query, we need to call refetch in all of them. And many queries could run when it's enough to run them once. (invalidate once)

Is it possible to subscribe for any kind of such events in Redux RTK query configuration?


Solution

  • You can trigger subscribed queries to re-query by invalidating their tags.

    See invalidateTags: A Redux action creator that can be used to manually invalidate cache tags for automated re-fetching.

    Assuming the getAllEmployees endpoint is providing a tag, e.g. "employees", you can dispatch an action to invalidate this tag and trigger the useGetAllEmployeesQuery hook to refetch data.

    const api = createApi({
      ...
      endpoints: builder => ({
        ...
        getAllEmployees: builder.query({
          query: ......,
          ...
          providesTags: ["employees"],
        }),
        ...
      }),
    });
    
    import { useEffect } from 'react';
    import { useDispatch } from 'react-redux';
    import api from '../path/to/apiSlice';
    
    export function useRefetchOnMessage (messageType = "", tags = []) {
      const dispatch = useDispatch();
    
      useEffect(() => {
        const handleMessage = () => {
          dispatch(api.util.invalidateTags(tags));
        };
    
        window.io?.socket?.on(messageType, handleMessage);
    
        return () => {
          window.io?.socket?.off(messageType, handleMessage);
        }
      }, [messageType, tags]);
    }
    

    The UI code need only call the hooks, passing the "employees" tag to the useRefetchOnMessage hook.

    const employees = useGetAllEmployeesQuery(officeId);
    useRefetchOnMessage('employees changed', ["employees"]);