how can I use routerMiddleware to pass history to redux. I have tried below but not working. I couldn't find any resources that integrate the routerMiddleware. Here, I am trying to react-admin dashboard where it asks to pass history as props to redux store. so I am trying to use routeMiddleware to pass the history props.
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import { routerMiddleware, connectRouter } from 'connected-react-router'
import { createBrowserHistory } from 'history'
import { postListReducer } from './reducers/postReducers'
const history = createBrowserHistory()
const reducer = combineReducers({
postList: postListReducer,
router: connectRouter(history),
})
const initialState = { postList: { posts: [] } }
const middleware = [thunk, routerMiddleware(history)]
const store = createStore(
reducer(history),
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
export default store
Reducers:
import {
POST_LIST_REQUEST,
POST_LIST_SUCCESS,
POST_LIST_FAIL,
POST_CREATE_REQUEST,
POST_CREATE_SUCCESS,
POST_CREATE_FAIL,
} from '../constants/postConstants'
export const postListReducer = (state = { posts: [] }, action) => {
switch (action.type) {
case POST_LIST_REQUEST:
return { loading: true }
case POST_LIST_SUCCESS:
return {
loading: false,
posts: action.payload,
}
case POST_LIST_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
Related
I am adding "add to cart" feature to the eCommerce site using redux-toolkit. I am also using local storage and thunk but it is not working. Products are not added to the cart. Nothing shows on the console. What is wrong with this code?
Here is the CartSlice.js code
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const cartItemsFromStorage = localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [];
const initialState = {
cartItems: cartItemsFromStorage,
};
export const cartSlice = createSlice({
name: "cartPage",
initialState,
reducers: {
addItem(state, action) {
const item = action.payload;
const existItem = state.cartItems.find((x) => x.product === item.product);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
},
},
});
export const { addItem } = cartSlice.actions;
export default cartSlice.reducer;
export function addToCart(id, qty) {
return async function addToCartThunk(dispatch, getState) {
try {
const { data } = await axios.get(`/api/products/${id}`);
dispatch(
addItem({
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
})
);
localStorage.setItem("cartItems", JSON.stringify(getState.cart));
} catch (error) {
console.log(error);
}
};
}
Here is the Store.js code
import { configureStore } from "#reduxjs/toolkit";
import productReducer from "./productSlice";
import productDetailReducer from "./productDetailSlice";
import cartReducer from "./cartSlice";
const store = configureStore({
reducer: {
productList: productReducer,
productDetails: productDetailReducer,
cartPage: cartReducer,
},
});
export default store;
Here is the CartScreen.js code
import React from "react";
import { Link } from "react-router-dom";
import { useEffect } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { addToCart } from "../store/cartSlice";
const EMPTY_CART = { cartItems: [] };
const CartScreen = () => {
const { productId } = useParams();
const location = useLocation();
const qty = location.search ? Number(location.search.split("=")[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart || EMPTY_CART);
const { cartItems } = cart;
console.log(cartItems);
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty));
}
}, [dispatch, productId, qty]);
return <div>Cart Page</div>;
};
export default CartScreen;
I'm trying to practise createAsyncThunk with reduxjs/tookit. When I first fetch the data from the api it works and I can render the data. However, when I refresh the page I get "TypeError: Cannot read properties of undefined (reading 'memes')" error and can't get it worked anymore. I looked up for some info and thought passing dispatch as useEffect dependency would help but it didn't. When I open Redux Devtools extension => diff = I can clearly see that I fetched the data, promise fulfilled and everything is fine. I try to log allMemes to console and it shows an empty object.
store.js
import { configureStore } from "#reduxjs/toolkit";
import memeSlice from "./features/getAllMemes/displayAllMemesSlice";
const store = configureStore({
reducer:{
memes:memeSlice
}
});
export default store;
DisplaySlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const loadAllMemes = createAsyncThunk("getMemes/loadAllMemes", async () => {
try {
const response = await fetch("https://api.imgflip.com/get_memes");
const data = await response.json();
return data;
}
catch (error) {
console.log(error)
}
})
export const memeSlice = createSlice({
name:"getMemes",
initialState: {
isLoading:false,
hasError:false,
allMemes:{},
},
extraReducers: {
[loadAllMemes.pending]:(state, action) => {
state.isLoading = true;
},
[loadAllMemes.fulfilled]:(state, action) => {
state.allMemes = action.payload;
state.isLoading = false;
},
[loadAllMemes.rejected]:(state, action) => {
state.hasError = true;
}
}
})
export default memeSlice.reducer;
export const selectAllMemes = state => state.memes.allMemes;
displayAllMemes.js
import React , {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadAllMemes, selectAllMemes } from './displayAllMemesSlice';
export default function DisplayAllMemes() {
const allMemes = useSelector(selectAllMemes)
const dispatch = useDispatch()
useEffect(() => {
dispatch(loadAllMemes())
console.log(allMemes)
}, [dispatch])
return (
<div>
{allMemes.data.memes.map(item => (
<h1>{item.id}</h1>
))}
</div>
)
}
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const loadAllMemes = createAsyncThunk("memes/getAllMemes", async () => {
try {
const response = await fetch("https://api.imgflip.com/get_memes");
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
export const memeSlice = createSlice({
name: "memes",
initialState: {
isLoading: false,
hasError: false,
allMemes: {},
},
extraReducers: {
[loadAllMemes.pending]: (state, action) => {
state.isLoading = true;
},
[loadAllMemes.fulfilled]: (state, action) => {
const { data, success } = action.payload;
state.allMemes = data;
state.isLoading = false;
},
[loadAllMemes.rejected]: (state, action) => {
state.hasError = true;
},
},
});
export const {} = memeSlice.actions;
export default memeSlice.reducer;
-- root reducer
import { combineReducers } from "redux";
import memes_slice from "./memes_slice";
const reducer = combineReducers({
memes: memes_slice,
});
export default reducer;
-- Component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { loadAllMemes } from "../../app/memes_slice";
function Memes() {
const { allMemes } = useSelector((state) => state.memes);
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadAllMemes());
}, [dispatch]);
console.log(allMemes.memes);
return <div>Memes</div>;
}
export default Memes;
Getting This Error When i use a useSelector hook to fetch data.
Im getting initial state undefined which cause the error.
selector.ts
import { createSelector } from '#reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';
const selectSlice = (state: RootState) => state.initialLoad || initialState;
export const selectInitialLoad = createSelector([selectSlice], state => state);
index.ts
import { PayloadAction } from '#reduxjs/toolkit';
import { createSlice } from 'utils/#reduxjs/toolkit';
import { useInjectReducer, useInjectSaga } from 'utils/redux-injectors';
import { initialLoadSaga } from './saga';
import { InitialLoadState, RepoErrorType } from './types';
export const initialState: InitialLoadState = {
name: 'initial-load',
layouts: [],
loading: false,
error: null,
};
const slice = createSlice({
name: 'initialLoad',
initialState,
reducers: {
loadLayout(state) {
state.loading = true;
state.error = null;
state.layouts = [];
},
layoutLoaded(state, action: PayloadAction<[]>) {
const repos = action.payload;
state.layouts = repos;
state.loading = false;
},
layoutError(state, action: PayloadAction<string | RepoErrorType>) {
state.error = 'error';
state.loading = false;
},
},
});
export const { actions: appLayoutActions } = slice;
export const useAppLayoutSlice = () => {
useInjectReducer({ key: slice.name, reducer: slice.reducer });
useInjectSaga({ key: slice.name, saga: initialLoadSaga });
return { actions: slice.actions };
};
types.ts
/* --- STATE --- */
export interface InitialLoadState {
name: string;
loading: boolean;
error?: RepoErrorType | string | null;
layouts: [];
}
export enum RepoErrorType {
RESPONSE_ERROR = 1,
USER_NOT_FOUND = 2,
USERNAME_EMPTY = 3,
USER_HAS_NO_REPO = 4,
GITHUB_RATE_LIMIT = 5,
}
/*
If you want to use 'ContainerState' keyword everywhere in your feature folder,
instead of the 'InitialLoadState' keyword.
*/
export type ContainerState = InitialLoadState;
reducers.ts
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { combineReducers } from '#reduxjs/toolkit';
import { InjectedReducersType } from 'utils/types/injector-typings';
/**
* Merges the main reducer with the router state and dynamically injected reducers
*/
export function createReducer(injectedReducers: InjectedReducersType = {}) {
// Initially we don't have any injectedReducers, so returning identity function to avoid the error
if (Object.keys(injectedReducers).length === 0) {
return state => state;
} else {
return combineReducers({
...injectedReducers,
});
}
}
this is my configuerStore.ts
/**
* Create the store with dynamic reducers
*/
import {
configureStore,
getDefaultMiddleware,
StoreEnhancer,
} from '#reduxjs/toolkit';
import { createInjectorsEnhancer } from 'redux-injectors';
import createSagaMiddleware from 'redux-saga';
import { createReducer } from './reducers';
export function configureAppStore() {
const reduxSagaMonitorOptions = {};
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
const { run: runSaga } = sagaMiddleware;
// Create the store with saga middleware
const middlewares = [sagaMiddleware];
const enhancers = [
createInjectorsEnhancer({
createReducer,
runSaga,
}),
] as StoreEnhancer[];
const store = configureStore({
reducer: createReducer(),
middleware: [...getDefaultMiddleware(), ...middlewares],
devTools: process.env.NODE_ENV !== 'production',
enhancers,
});
return store;
}
work around
adding a condition to state
import { createSelector } from '#reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';
const selectSlice = (state: RootState) => state && state.initialLoad || initialState;
export const selectInitialLoad = createSelector([selectSlice], state => state);
most of the files are of create react app boilerplate not change.
i tried the same on the cra boilerplate template without clean setup and it worked fine but after clean and setup it doesnt work how i need it too
Please define initialLoad into /src/types/RootState.ts
Example here
I was trying to make a store but for some reason the initial state doesnt change when i alter the inicial state const:
this is the reducer with the inicial state:
import { GET_PRODUCT_BY_NAME, ADD_PRODUCT_TO_CART, CLEAR_CART, DELETE_ITEM, CURRENT_PRODUCT,INCREASE_AMOUNT } from '../../actionTypes'
const INITIALSTATE = {
currentProduct:{},
product: {},
list:[]
}
export function ProductReducer(state = INITIALSTATE, action) {
const { type, payload } = action
switch (type) {
case GET_PRODUCT_BY_NAME:
return {
...state,
user: payload.user,
}
case ADD_PRODUCT_TO_CART:
return {
...state,
list: [...state.list,payload]
}
case CLEAR_CART:
return {
list: []
}
case DELETE_ITEM:
console.log()
return {
list:[...state.list.slice(0, action.payload),
...state.list.slice(action.payload + 1)]
}
case CURRENT_PRODUCT:
return {
...state,
currentProduct: payload
}
case INCREASE_AMOUNT:
return {
...state,
}
default:
return state
}
}
this is the file with the store config details:
import { createStore, applyMiddleware } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import ReduxThunk from 'redux-thunk'
import storage from 'redux-persist/lib/storage'
import rootReducer from './rootReducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
let store = createStore(persistedReducer, applyMiddleware(ReduxThunk))
let persistor = persistStore(store)
export { store, persistor }
and this is what i get when i console.log the state:
console.log
that result is from an older version
you encountered that issue because you had applied persistence before you settled on how your Product reducer initial state should look like. to solve this you need to either temporarily include your reducer into the persistConfig blacklist or setting the stateReconciler to autoMerge.
I defined setUser action in chatActions.js file below. From the console log, I can see the setUser action is fired. But corresponding reducer is never triggered.
export const SET_USER = "SET_USER";
export const CLEAR_USER = "CLEAR_USER";
export const setUser = user => {
console.log("In setUser"); <=== setUser action is fired for sure
return {
type: SET_USER,
payload: {
currentUser: user
}
};
};
export const clearUser = () => {
return {
type: CLEAR_USER
};
};
The reducer page is like below. From the console, I can see currentUser and isLoading being set to its initial state. But the state is never updated nevertheless the setUser action is fired.
import { createReducer } from '../../../app/common/util/reducerUtil'
import { SET_USER, CLEAR_USER, setUser, clearUser } from "../actions/chatActions"
const initialUserState = {
currentUser: null,
isLoading: "true"
};
export const chatUserReducer = (state = initialUserState, action) => {
switch (action.type) {
case SET_USER:
return {
currentUser: action.payload.currentUser,
isLoading: false
};
case CLEAR_USER:
return {
...state,
isLoading: false
};
default:
return state;
}
};
export default createReducer(initialUserState, {
[SET_USER]: setUser,
[CLEAR_USER]: clearUser
})
Here is the rootReducer page:
import { combineReducers } from 'redux';
import { reducer as FormReducer } from 'redux-form';
import chatUserReducer from '../../features/chat/reducers/chatUserReducer';
const rootReducer = combineReducers(
user: chatUserReducer
})
export default rootReducer;
The action is fired in the ChatDashboard component. user isn't null, and from above we can see setUser action is triggered, but somehow reducer isn't.
import { setUser, clearUser } from "../../chat/actions/chatActions";
const actions = {
setUser,
clearUser
}
const mapState = (state) => ({
currentUser: state.user.currentUser
})
class ChatComponent extends Component {
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) { <=== user is not null, this if condition is fulfilled
this.props.setUser(user);
}
});
}
...
}
export default withRouter(connect(mapState, actions)(ChatComponent));
You need to import chatUserReducer like:
import {chatUserReducer} from '../../features/chat/reducers/chatUserReducer';
RootReducer will be like this
import { combineReducers } from 'redux';
import { reducer as FormReducer } from 'redux-form';
import {chatUserReducer} from '../../features/chat/reducers/chatUserReducer';
const rootReducer = combineReducers(
user: chatUserReducer
})
export default rootReducer;