Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connecting RTK Query API with redux reducer and selector

I think I'm missing something with regards to Redux and RTK Query tools. I'm also using RTK Query OpenAPI codegen for this.

Right now I have an API that looks like this ->

export const api = generatedApi.enhanceEndpoints({
  endpoints: {
    getMaterials: {
      transformResponse: response => normalize(response),
    },
  },
})

This gives me back normalized data fine in my component:

const Materials = () => {
  const { data, isLoading, error } = useGetMaterialsQuery()

  /*
    Cool this gives me data back like:
    {
      [id]: {
        id,
        prop1: 'blah',
        prop2: 'blah2'
      }
    }
  */

  console.log(data)

  // but now I want to structure this data differently using selector

  const newDataStructure = useSelector(addSomeMetaDataAndStructureDifferentlySelector)

  return <MyComponent structuredData={newDataStructure} />
}

But when I look at state in that selector it looks like:

data snippet

When in the selector I really want to use something like ->

const addSomeMetaDataAndStructureDifferentlySelector = state => 
    // create new data structure from state.materials.byId that I will pass to `MyComponent`

My store looks like this right now ->

import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query'
import { api } from 'gen/rtk-openapi'
// import materialsReducer from 'state/materials/materialsSlice'

const store = configureStore({
  reducer: {
    [api.reducerPath]: api.reducer,
  },
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(api.middleware),
})

setupListeners(store.dispatch)

export default store

So do I need to pass the data directly from useGetMaterialsQuery into a function and just transform it?

Or can I somehow create a new reducer slice that somehow initializes with the data correctly so that I can access state.materials.byId in a selector?

Or am I fundamentally missing something here?

like image 356
Evan Avatar asked Jul 23 '21 21:07

Evan


1 Answers

You missed an important bit: RTK-Query is not a normalized cache, but a Document cache.

It caches your endpoints and each request to those, using the request arguments as cache keys. Each of those responses is treated as a "document" and cached separately.

That might seem like a drawback, but doing a real "normalized" cache is almost impossible as a generic library, especially for something ambiguous as REST apis. Even for GraphQL it is a very hard problem and even solutions specialized on GraphQL (like apollo) have lots of problems and end up with lots of hand-written code to handle cases like additions/removals from collections.

So in the end, you are left with two choices: write your own normalized caching solution by hand, explicitly geared towards your data structures or use a document cache.

In most situations, a document cache is just enough. And that's where RTK-Query comes in and tries to give you as many tools as possible to keep data "in sync" between your documents: automated refetching and where you want to avoid that the option to do manual optimistic updates to manipulate specific cache entries.

Generally, I would recommend against copying that data over to hand-written slices. You will lose a lot of benefits of RTK-Query that way, for example subscription tracking and automatic cache cleanup after no more components use a value after 60 seconds. It might sound uncommon after using Redux with hand-written slices for a while, but it works very well in most situations - with a lot less code.

As for how to use it: Generally, you just call your useGetMaterialsQuery() in all components that need the materials and just filter out what you need in your component, or use the selectFromResult option to just select what you really need.

like image 121
phry Avatar answered Oct 20 '22 14:10

phry