Search code examples
javascriptreactjsreduxredux-toolkitrtk-query

Can i subscribe to filtered RTK query data results across multiple components?


I have a series of components which all use data derived from an RTK Query response. While derived from the same response with the same query arguments, each component needs the data to pass through a set of relatively expensive client-side filters (whose args are redux store slice properties) which may not be the same for each component. However, all of the components require the data to pass through at least two specific filters. This relationship is shown by the following diagram:

Is it possible to subscribe to the data after it has been transformed by a specific filter/ set of filters?

enter image description here

Approaches I have considered:

  • Use query in all components, apply whichever filters are required in some useEffect or useMemo. This is not preferable since that means at least 2 filters are being duplicated nComponents times.
  • Use createSlice extraReducers option and listen for query completion, then perform filter operation. This works fine since i can use the filter args in the reducer, but I believe there is no way for me to repeat the operation with new args once the filter args have been updated but the query data has stayed the same.
  • Subscribe one component, publish data to slice after each corresponding filter stage, subscribe each component to corresponding data. This is how i currently have this implemented, but it is not ideal because it couples components together which i wish to avoid, bloats one component which is sort of arbitrarily chosen, and produces frequent large state actions which slow down my application.
  • Raise query subscription to shared ancestor component, then pass data as props. This is not ideal because these components are at varying depths relative to their shared ancestor, which I imagine would result in prop drilling for at least some component.
  • Use react context to share results of first 2 filter operations with corresponding components. Haven't looked into this much yet; would it work with a query subscription?

Intuitively i would think some callback that operates as some middleware between the API result and the component's subscribed data would be ideal. I am aware of the transformResponse option definable in the API slice, but I believe it is not appropriate or possible for this situation.

const queryResult = endpointName.useQuery(args, filterArgs, (data, filterArgs) => {
    return data.performSomeSharedFilterOperationHere(filterArgs);
    } 
);

Ideally the data would update when query args change OR when filter args change. I suppose the difference between this and a simple useEffect implementation is that in the useEffect scenario, the data is not 'shared' and the filter operations occur nSubscribedComponents times.

Is there anything in RTK that permits the behavior I am seeking?


Solution

  • I think the right answer here is to use the selectFromResult option in the query hooks.

    Create the following Reselect selectors:

    
    const selectFilter1 = createSelector(
      // Start by taking the actual response data as its input
      (inputData) => inputData,
      (data) => // magically transform here
    )
    
    const selectFilter2 = createSelector(
      selectFilter1,
      (filteredData) => // magically transform here
    )
    
    // repeat for filters 2 and 3
    

    Then, in the component:

      const { filteredData} = useGetPostsQuery(undefined, {
        selectFromResult: result => ({
          // We can optionally include the other metadata fields from the result here
          ...result,
          // Include a field called `filteredData` in the result object, 
          // and memoize the calculation
          filteredData: selectFilter3(result.data)
        })
      })
    

    The components will be sharing the same selector instance, so each time the selector is called the same result.data reference should be passed in, and thus the calculations should memoize. The first couple selectors should memoize their results, and thus selectFilter3 will only have to recalculate when selectFilter2's result changes, etc.