I have currently the following store creation function
export const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(saga).concat(thunk)
});
I try to add redux-persist to this app. When doing it up to general instruction I have received error which informed that some actions can not be serialised. It seems that solution is
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
However I can not realize how both could be combined syntactically.
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(saga).concat(thunk).concat(serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
})
});
is not valid. Could I ask someone for assistance?
middleware: (getDefaultMiddleware) =>
// here you start calling `getDefaultMiddleware`
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
})
// here that call to getDefaultMiddleware is finished - everything until here is a method argument
.concat(saga)
,
and you really do not need .concat(thunk) as redux-thunk is included in getDefaultMiddleware - it's a RTK default.
Also, please note that we recommend sagas only for extremely complicated asynchronous logic. In most modern applications you probably will not have any need for redux-saga.
Usually you should go with thunks and if that is not enough, Redux Toolkit now ships with the listener middleware that allows you to do most advanced things sagas were used for before. But for normal data fetching, createApi or createAsyncThunk should be enough.
Related
I have a React app where I've used the #rtk-query/codegen-openapi tool to generate my authentication queries and mutations. I'm also using Redux Persist to store some of this data.
I'm trying to figure out a way to invalidate this data programmatically when the user logs out. Is it possible to do this?
My redux store configuration looks something like this:
const persistConfig = {
key: "root",
storage,
blacklist: ["api"],
};
const authPersistConfig = {
key: "auth",
storage,
whitelist: [],
};
const rootReducer = combineReducers({
settings: settingsSlice.reducer,
[applicationApi.reducerPath]: applicationApi.reducer,
[enhancedAuthenticateApi.reducerPath]: persistReducer(
authPersistConfig,
enhancedAuthenticateApi.reducer
),
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const buildStore = () =>
configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat([enhancedAuthenticateApi.middleware]),
});
I'm pretty new to Redux and RTK Queries so any help is appreciated.
I have the following createSlice method. I looked into the documentation of createSlice where they have given an option to use the prepare method to customize the action creation.
I'm trying to make an API call, However the reducer is triggered before the API response. Hence my action.payload remains undefined.
Can prepare be used to make async calls ?
PS: I did not want to maintain my customized action creators in a separate function/file. The purpose of using createSlice is to improve code maintainability.
export const authReducers = createSlice({
name: "auth",
initialState: { ...initialState },
reducers: {
loginToConsole: {
reducer: (state, action) => {
console.log("Reducer Called", action);
state.isLoggedIn = !state.isLoggedIn;
},
prepare: async (credentials) => {
let response = await axios.post(
`${apiEndPoint}/api/auth/login`,
credentials
);
console.log(response);
return { payload: { ...response.data } };
},
},
},
});
No, it cannot. prepare is called synchronously.
You should use thunks for that - see This Chapter of the official Redux tutorial
That said, nothing prevents you from writing your thunks just in the same file. Many people do that.
What will happen when i use Redux Toolkit Query with redux-persist?
Will it use the persisted state or will the state be refetched?
There is an official guide for this now. At the time of writing this, you could configure it this way:
// store.ts
import { configureStore, combineReducers } from '#reduxjs/toolkit'
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist'
import AsyncStorage from '#react-native-async-storage/async-storage'
import someSlice from 'config/features/someSlice'
import { api } from 'config/services/api'
const reducers = combineReducers({
someSlice,
[api.reducerPath]: api.reducer,
})
const persistConfig = {
key: 'root',
version: 1,
storage: AsyncStorage,
}
const persistedReducer = persistReducer(persistConfig, reducers)
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
// Redux persist
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(
api.middleware,
),
})
export let persistor = persistStore(store)
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// App.tsx for native or index.tsx
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
I just really wouldn't do it.
That restored data could be all kinds of stale and when a user hits F5 they usually expect data to be up-to-date, not a week old or something. Also, while the store slice is restored, information about subscriptions might be problematic (because the "subscribing components" never exist, they can also never unmount and thus get never cleaned from the store).
So, I'd blacklist the api slice from being persisted.
If you want that stuff to be cached, do it with cache headers in your server. The browser will do all the caching for you, but also allow the user to clear the cache or force a refetch with ctrl+shift+r - so the browser would just behave more than the user expects.
Use extractRehydrationInfo like this:
import { REHYDRATE } from 'redux-persist'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === REHYDRATE) {
return action.payload[reducerPath]
}
...
},
Full official guide here.
Hey fellow programmers,
Been having fun learning react-redux lately, but I do have one question that bothers me.
My understanding is that, by using createAsyncThunk it will automatically generates action type constants. (pending, fulfilled, and rejected)
What I wanted to know is that is there any way to manually dispatch action type during createAsyncthunk , so that we can have more flexibility in our code.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId).then(
...
dispatch(fulfilled) // is this possible ?
).catch(
dispatch(rejected) // is this possible ?
)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
...,
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[fetchUserById.fulfilled]: (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
}
}
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
The point of createAsyncThunk is that it generates those action types, _and dispatches them for you automatically. You definitely do not need to dispatch(fulfilled()) yourself, because that's what createAsyncThunk does for you - you just need to return a promise that either resolves or reject, and it dispatches the fulfilled/rejected action types based on that.
You do get access to thunkAPI.dispatch, so you can dispatch other actions if necessary, but you don't need to worry about the fulfilled/rejected actions yourself.
I'm using the omniauth-github strategy and upon a button click I want to dispatch an action to another domain, (such as 'https://github.com/login/oauth/authorize'). When using dispatch this however does not work as the browser preflights my request and resonds with 'No 'Access-Control-Allow-Origin'. I can get this to work by using an and point to the url, which then will send the user back to my backend to authenticate the user get the token store it. But without dispatch, I have to send back the JWT token my site generates in query params, and since I am omitting my action creators and reducers, I cannot store it in localStorage. Is there any way to perform dispatch cross domain?
export const loginGitHub = () => {
return dispatch => {
fetch('https://github.com/login/oauth/authorize?client_id=...&scope=user',{
method: 'GET',
headers: {
'Access-Control-Allow-Origin': '*',
},
mode: 'cors'
})
.then(resp=>resp.json())
.then(data => {
debugger
})
}
}
You'll need to provide your redux store's dispatch method to this method for it to work, this is typically done by using mapDispatchToProps with redux's connect() method: https://github.com/reduxjs/react-redux/blob/master/docs/api.md
That's the typical flow, if for some reason you need to call this outside a component like before you mount your React app (but after you've initialized your redux store) something like this can work:
import { createStore } from 'redux'
const store = createStore();
export const loginGitHub = dispatch => {
return dispatch => {
fetch('https://github.com/login/oauth/authorize?client_id=...&scope=user',{
method: 'GET',
headers: {
'Access-Control-Allow-Origin': '*',
},
mode: 'cors'
})
.then(resp=>resp.json())
.then(data => {
debugger
})
}
}
loginGitHub(store.dispatch);
That's very much an anti pattern, and I'd recommend properly using mapDispatchToProps which requires
Creating a store
Wrapping your app in a provider and providing the previously created store as a prop to the provider.
Using connect() like so within your component:
import React, { Component } from 'react';
import { connect } from 'redux';
import { loginGitHub } from './wherever';
class ExampleComponent extends Component {
// whatever component methods you need
}
const mapDispatchToProps = dispatch => ({
loginGitHub: () => dispatch(logInGitHub())
})
export default connect(null, mapDispatchToProps)(ExampleComponent);
Then you'll be able to call loginGitHub with this.props.loginGitHub() within your component.