Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rtk-query fetch data Stored in the redux-persist #1400

Closed
kuangshp opened this issue Aug 12, 2021 · 12 comments
Closed

rtk-query fetch data Stored in the redux-persist #1400

kuangshp opened this issue Aug 12, 2021 · 12 comments

Comments

@kuangshp
Copy link

I want to persist the data requested by RTk-Query into the browser's localstorage. I've already done that using redux-persist, but looking at the data in localstorage, Not the data that I want to return in transformResponse, but one that includes a lot of state classes

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

interface IPostVo {
  id: number;
  name: string;
}

const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'http:https://localhost:5000' }),
  keepUnusedDataFor: 5 * 60,
  refetchOnMountOrArgChange: 30 * 60,
  tagTypes: ['Post'],
  endpoints: (builder) => ({
    // post list
    getPostsList: builder.query<Promise<IPostVo[]>, void>({
      query: () => '/posts',
      transformResponse: (response: { data: Promise<IPostVo[]> }) => {
        return response.data;
      },
    }),
  }),
});

export const {
  useGetPostsListQuery,
} = postsApi;
export default postsApi;
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import logger from 'redux-logger';
import { setupListeners } from '@reduxjs/toolkit/dist/query/react';
import {
  MiddlewareAPI,
  isRejectedWithValue,
  Middleware,
} from '@reduxjs/toolkit';

import postsApi from './posts';
import userApi from './user';

const persistConfig = {
  key: 'root',
  storage,
};
const rootReducer = combineReducers({
  [postsApi.reducerPath]: postsApi.reducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);


const rtkQueryErrorLogger: Middleware =
  (api: MiddlewareAPI) => (next) => (action) => {
    // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these use matchers!
    if (isRejectedWithValue(action)) {
      console.warn('We got a rejected action!');
      // toast.warn({ title: 'Async error!', message: action.error.data.message });
    }

    return next(action);
  };

const middlewareHandler = (getDefaultMiddleware: any) => {
  const middlewareList = [
    ...getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST'],
      },
    }),
    postsApi.middleware,
    rtkQueryErrorLogger,
  ];
  if (process.env.NODE_ENV === 'development') {
    middlewareList.push(logger);
  }
  return middlewareList;
};

export const rootStore = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) => middlewareHandler(getDefaultMiddleware),
});

export const persistor = persistStore(rootStore);
export type RootState = ReturnType<typeof rootStore.getState>;

setupListeners(rootStore.dispatch);

image

@msutkowski
Copy link
Member

I'm not sure what the actual question here is, but @phryneas has commented on this in multiple places. I'd recommend reading this: https://stackoverflow.com/questions/67943867/what-happens-when-i-use-rtk-query-with-redux-persist

@kuangshp
Copy link
Author

I'm not sure what the actual question here is, but @phryneas has commented on this in multiple places. I'd recommend reading this: https://stackoverflow.com/questions/67943867/what-happens-when-i-use-rtk-query-with-redux-persist

I hope that the data stored in the store is only the interface data returned by the back end, and there is no loading or status。eg.
image

@phryneas
Copy link
Member

phryneas commented Aug 14, 2021

@kuangshp That does not even seem to be a RTK-Query internal state, but some derived data...?

I don't know what you are persisting there. But generally, even with us adding support for rehydration in the future, you will have to persist the data structures as they are, not just whatever you like.

(And generally, you probably should not be persisting an api cache at all)

@jonra1993
Copy link

Hello @kuangshp were you able to work rtk-query with redux-persists correctly I also need to make it work together in my react native app. I need redux-persists to save some data and for future offline support.

I configure similar to your code but I see that after some time I enter again to my component but it does not refetch new data.

@phryneas
Copy link
Member

@jonra1993 generally I can only advise against persisting RTK-Query state at this moment.

It will be possible in RTK 1.7 to rehydrate with helpers we will be providing, but with the current version of RTK if you just persist and rehydrate it, you will also rehydrate component subscriptions of components that are not there (so stuff will never be cache-collected) and even worse, if you rehydrate a query that was currently in "pending" state, that state will never be left and it will be impossible to fetch that state again.

You can install the experimental build from #1277 and configure rehydration like shown in https://github.com/phryneas/ssr-experiments/blob/126262fcce7f8b788df3670dbc061362c65b6b9e/nextjs-blog/lib/pokemonApi.ts#L8-L12 , but that might still change a bit before release.

@jonra1993
Copy link

jonra1993 commented Sep 29, 2021

Hello, @phryneas thanks for the response I am going to check it. I found an alternative solution meanwhile. I have a persistent reducer and others not persisted using rtk-query. More description can be found here maybe someone is facing a similar issue.
https://stackoverflow.com/questions/69380121/middleware-for-rtk-query-api-at-reducerpath-api-has-not-been-added-to-the-stor

@phryneas
Copy link
Member

You can also just persist the whole store, but blacklist the RTK Query slice. That should work as well.

@jonra1993
Copy link

Hello @phryneas when I do that I get this warning
image

@phryneas
Copy link
Member

@jonra1993
you get the upper warning because there is a lot of stuff in your state and you might need to exclude the api slice from the development middlewares if there is so much data in there

you get the lower warning because you keep persisting and restoring RTK-Q using a non-supported method of doing so. you will have the state from last application and the middleware from the current application and they do not belong together.

@jonra1993
Copy link

jonra1993 commented Sep 30, 2021

Hello, @phryneas thanks for your recommendation was the best "using the blacklist". I reset states on logout and due to I located API slides on configureStore they were not affected and the cache was not invalidated. "resetAppAction" is dispatched on logout. This current setup worked perfectly but sometimes I get the second warning.

store.ts

import { useDispatch as useReduxDispatch, useSelector as useReduxSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { ThunkAction } from 'redux-thunk';
import type { Action } from '@reduxjs/toolkit';
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query/react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from "redux-persist"
import { encryptTransform } from 'redux-persist-transform-encrypt';
import thunk from 'redux-thunk';
import { ENABLE_REDUX_DEV_TOOLS } from 'src/constants';
import rootReducer from './rootReducer';
import { storiesApi } from 'src/services/storiesService'

const encryptor = encryptTransform({
  secretKey: 'my-super-secret-key',
  onError: function (error) {
    // Handle the error.
  },
});

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  blacklist: [
    storiesApi.reducerPath, 
  ],
  timeout: null,
  transforms: [encryptor],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  devTools: ENABLE_REDUX_DEV_TOOLS,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(
      thunk,
      storiesApi.middleware,
    ),
});

// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)

export const persistor = persistStore(store);

export type RootState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

export type AppThunk = ThunkAction<void, RootState, null, Action<string>>;

export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector;

export const useDispatch = () => useReduxDispatch<AppDispatch>();

rootReducer.ts

import { combineReducers } from '@reduxjs/toolkit';
import { reducer as authReducer } from 'src/redux/slices/auth';
import { reducer as storiesReducer } from 'src/redux/slices/stories';
import { storiesApi } from 'src/services/storiesService'

const appReducer = combineReducers({
  auth: authReducer,
  stories: storiesReducer,
  [storiesApi.reducerPath]: storiesApi.reducer,
});

const rootReducer = (state, action) => {
  if (action.type === 'RESET_APP') {
    state = undefined;
  }
  return appReducer(state, action);
};

export const resetAppAction = () => dispatch => {
  console.log('Reset Redux Store');
  dispatch({ type: 'RESET_APP' });
};

export default rootReducer;

@jonra1993
Copy link

jonra1993 commented Sep 30, 2021

@phryneas Currently I am trying to create an infinite scroll list, so each time a new response arrives it should be appended into the last state as a concatenation. lastApiState = lasApiState.concat(newApiResponse)

Is there any way to create an action that is able to generate a dispatch that mutates the apiState manually not using an API query? I made use of extraReducers in a slide in order to have a 'copy' of states each time a new Api response arrives, but I think if there is any way to mutated API state it could be better. Please let me know if there is any way I can do that. Thanks in advance.

const slice = createSlice({
  name: 'stories',
  initialState,
  reducers: {
  },
  extraReducers: (builder) => {
    builder.addMatcher(storiesApi.endpoints.getStories.matchFulfilled,
      (state: IStoriesState, action: PayloadAction<IListResponse>) => {
        const data = action.payload;
        console.log('addMatcher getStories')
        const lastStories = [...state.posts];
        if(data.currentPage === 1) state.posts = data.results;
        else state.posts = [...lastStories, ...data.results];
      }
  },
});

@jonra1993
Copy link

jonra1993 commented Oct 1, 2021

Do not worry I found your response on StackOverflow I am going to try to implement it
https://stackoverflow.com/questions/67909356/is-there-any-way-to-fetch-all-the-responses-stored-in-api-slice-rtk-query

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants