JSPM

  • Created
  • Published
  • Downloads 8
  • Score
    100M100P100Q53959F
  • License MIT

Powerful data fetching and caching library that supports normalization, built on top of redux

Package Exports

  • react-redux-cache
  • react-redux-cache/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (react-redux-cache) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

react-redux-cache

Powerfull and customizable data fetching and caching library that supports normalization (unlike react-query and rtk-query), built on top of redux.

Normalization is the only way to keep the state of the app consistent between different views, reduces the number of fetches and allows to show cached data when navigating, which greatly improves user experience.

Hooks, reducer, actions and selectors are fully typed and written on Typescript, so redux store will be properly typed and you will remain a full control of its state with ability to write custom selectors, actions and reducers to manage cached state.

Usage example can be found in example/ folder and run by npm run example command from the root folder.

Table of contents

Installation

react and redux are peer dependencies.

npm add react-redux-cache react redux

Initialization

Create reducer, hooks, actions and selectors with createCache. All queries and mutations should be passed while initializing the cache, for proper typing and later access by key. In this example we omit usage of actions and selectors.

cache.ts

export const {
  reducer,
  hooks: {useMutation, useQuery, useSelectEntityById},
} = createCache({
  // This selector should select cache state from redux store state, based on the path to its reducer.
  cacheStateSelector: (state) => state.cache,
  // Typenames provide a mapping of all typenames to their entity types.
  // Empty objects with type casting can be used as values.
  typenames: {
    users: {} as User,
    banks: {} as Bank,
  },
  queries: {
    getUsers: { query: getUsers },
    getUser: { query: getUser },
  },
  mutations: {
    updateUser: { mutation: updateUser },
    removeUser: { mutation: removeUser },
  },
})

store.ts

const store = configureStore({
  reducer: {
    cache: reducer,
  }
})

api.ts

Query result should be of type QueryResponse, mutation result should be of type MutationResponse. For normalization normalizr package is used in this example, but any other tool can be used if query result is of proper type. Perfect implementation is when the backend already returns normalized data.

export const getUser = async (id: number) => {
  const result: User = await ...
  
  const normalizedResult: {
    result: number
    entities: {
      users: Record<number, User>
      banks: Record<string, Bank>
    }
  } = normalize(result, getUserSchema)

  return normalizedResult
}

export const removeUser = async (id: number) => {
  await ...
  return {
    remove: { users: [id] },
  }
}

Usage

UserScreen.tsx

export const UserScreen = () => {
  const {id} = useParams()

  // useQuery will infer all types from created cache,
  // telling you that params and result here are of type `number`.
  const [{result: userId, loading, error}] = useQuery({
    query: 'getUser',
    params: Number(id),
  })

  const [updateUser, {loading: updatingUser}] = useMutation({
    mutation: 'updateUser',
  })

  // This selector is created by createCache and also returns proper types - User and Bank
  const user = useSelectEntityById(userId, 'users')
  const bank = useSelectEntityById(user?.bank, 'banks')

  if (loading) {
    return ... // loading state
  }

  return ... // loaded state
}

Advanced

To be done...