I'm using React and Redux for creating a shop website. I want to add items to the cart using redux, but cart items it's not getting added only showing an error:
GET http://localhost:3000/api/v1/product/61db003374fe4392e3446ae3 404 (Not Found)
"Uncaught (in promise) Error: Request failed with status code 404"
and I suppose the problem is maybe with ID in action but I'm not sure how to fix it.
Please help me out. Below is my code
// reducer
import { ADD_TO_CART } from '../constants/cartConstants';
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.type) {
case ADD_TO_CART:
const item = action.payload;
const isItemExist = state.cartItems.find(
(i) => i.product === item.product
);
if (isItemExist) {
return {
...state,
cartItems: state.cartItems.map((i) =>
i.product === isItemExist.product ? item : i
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
default:
return state;
}
};
// actions
import axios from 'axios';
import { ADD_TO_CART } from '../constants/cartConstants';
export const addItemToCart = (id, quantity) => async (dispatch, getState) => {
const { data } = await axios.get(`/api/v1/product/${id}`);
dispatch({
type: ADD_TO_CART,
payload: {
product: data.product._id,
name: data.product.name,
price: data.product.price,
image: data.product.images[0].url,
stock: data.product.stock,
quantity,
},
});
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems));
};
// store
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import {
productsReducer,
productsDetailsReducer,
} from './reducers/productReducers';
import {
authReducer,
userReducer,
forgotPasswordReducer,
} from './reducers/userReducers';
import { cartReducer } from './reducers/cartReducers';
const reducer = combineReducers({
products: productsReducer,
productDetails: productsDetailsReducer,
auth: authReducer,
user: userReducer,
forgotPassword: forgotPasswordReducer,
cart: cartReducer,
});
let initialState = {
cart: {
cartItems: localStorage.getItem('cartItems')
? JSON.parse(localStorage.getItem('cartItems'))
: [],
},
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
Related
Slice
import { createSlice } from '#reduxjs/toolkit';
const initialState: { value: number } = { value: 0 };
export const testSlice = createSlice({
name: 'test',
initialState,
reducers: {
test: (state) => {
state.value += 1;
},
},
});
export const { test } = testSlice.actions;
Combine
import { combineReducers } from '#reduxjs/toolkit';
import { authSlice } from './auth';
import { productsSlice } from './products';
import { testSlice } from './test';
import { userSlice } from './user';
export const rootReducer = combineReducers({
user: userSlice.reducer,
auth: authSlice.reducer,
products: productsSlice.reducer,
test: testSlice.reducer,
});
export type RootState = ReturnType<typeof rootReducer>;
Store
import { configureStore } from '#reduxjs/toolkit';
import storage from 'utils/storage';
import { rootReducer } from './rootReducer';
declare global {
interface Window {
store: any;
}
}
const reHydrateStore = () => {
if (storage.getToken() !== null) {
return storage.getToken();
}
return false;
};
// !TODO add types
// #ts-ignore
const localStorageMiddleware = (store) => (next) => (action) => {
if (action.type.match('auth')) {
const result = next(action);
storage.setToken(store.getState().auth.token);
return result;
}
return next(action);
};
export const store = configureStore({
reducer: rootReducer,
preloadedState: {
auth: {
token: reHydrateStore() || '',
loading: false,
},
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(localStorageMiddleware),
});
window.store = store.getState();
When I output store.test to the console, the value remains old, but auth reducer works correctly.
Also I see that the state after the update in the reducers is 0.
Why is state not being updated?
Above are examples of my code:
Сreate Slice
Combine reducers
Store settings (and output store in console)
I am trying to add products to shopping cart by storing them in local storage but, they are not getting stored. I looked into Redux-dev-tools and found out my state is not updating:
As you can see action is getting fired but my state is not updating:
Here is the source code:
cartAction.js
import axios from "axios"; import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const addToCart = (id, qty) => async (dispatch, getState) => { const { data } = await axios.get(`/api/product/${id}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
productID: data.product._id,
name: data.product.name,
image: data.product.image,
price: data.product.price,
countInStock: data.product.countInStock,
qty,
}, });
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems)); };
cartReducer.js
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.state) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find(
(x) => x.productID === item.productID
);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.productID === existItem.productID ? item : x
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
// case CART_REMOVE_ITEM:
// return {};
default:
return state;
}
};
store.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
// reducers
import {
productDetailsReducer,
productListReducer,
} from "./reducers/productReducers";
import { cartReducer } from "./reducers/cartReducers";
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer,
cart: cartReducer,
});
const cartItemsFromStorage = localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [];
const initialState = {
cart: { cartItems: cartItemsFromStorage },
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
CartScreen.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addToCart, removeFromCart } from "../../redux/actions/cartActions";
import { Link } from "react-router-dom";
import ErrorMessage from "../../components/ErrorMessage/ErrorMessage";
import "./CartScreen.scss";
const CartScreen = ({ match, location, history }) => {
const productID = match.params.id;
const qty = location.search ? Number(location.search.split("=")[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
console.log(cartItems);
useEffect(() => {
if (productID) {
dispatch(addToCart(productID, qty));
}
}, [dispatch, productID, qty]);
return (
<>
<h1>Shopping Cart</h1>
</>
);
};
export default CartScreen;
You need to fix this on CartReducer.js
switch (action.state) {
to
switch (action.type) {
See image explanation of problem
Problem:
The auth reducer is not getting recognized in the global store when imported & attempted to combine with the other reducers. Where the other reducers such as alert, profile & post have been recognized by the global store.
Auth Reducer:
Github: https://github.com/DariusRain/Pluto/blob/master/client/src/redux/modules/auth.js
import api from "../../utils/api";
import { setAlert } from "./alert";
// Action Types
export const REGISTER_SUCCESS = "PLUTO/AUTH/REGISTER_SUCCESS";
export const REGISTER_FAIL = "PLUTO/AUTH/REGISTER_FAIL";
export const LOGIN_SUCCESS = "PLUTO/AUTH/LOGIN_SUCCESS";
export const LOGIN_FAIL = "PLUTO/AUTH/LOGIN_FAIL";
export const USER_LOADED = "PLUTO/AUTH/USER_LOADED";
export const AUTH_ERROR = "PLUTO/AUTH/ERROR";
export const LOGOUT = "PLUTO/AUTH/LOGOUT";
export const ACCOUNT_DELETED = "PLUTO/AUTH/ACCOUNT_DELETED";
// Reducer
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null,
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: payload,
};
case REGISTER_SUCCESS:
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case LOGIN_SUCCESS:
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case ACCOUNT_DELETED:
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
};
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGOUT:
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
};
default:
return state;
}
};
// Action Creators
export const loadUser = () => async (dispatch) => {
try {
const res = await api.get("/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
// Register User
export const register = (formData) => async (dispatch) => {
try {
const res = await api.post("/users", formData);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: REGISTER_FAIL,
});
}
};
// Login User
export const login = (email, password) => async (dispatch) => {
try {
const res = await api.post("/auth", { email, password });
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: LOGIN_FAIL,
});
}
};
// Logout
export const logout = () => async (dispatch) => dispatch({ type: LOGOUT });
Combine Reducers
Github: https://github.com/DariusRain/Pluto/blob/master/client/src/redux/modules/index.js
import {
combineReducers
} from "redux";
import alert from "./alert";
import post from "./post";
import profile from "./profile";
import auth from "./auth";
export default combineReducers({
alert,
profile,
post,
auth,
});
Store
Github: https://github.com/DariusRain/Pluto/blob/master/client/src/redux/index.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './modules';
import setAuthToken from '../utils/setAuthToken';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
let currentState = store.getState();
store.subscribe(() => {
let previousState = currentState;
currentState = store.getState();
if (previousState.auth.token !== currentState.auth.token) {
const token = currentState.auth.token;
setAuthToken(token);
}
});
export default store;
Repository:
Pluto: https://github.com/DariusRain/Pluto
Help appreciated! 😁 -DariusRain
You have a circular dependency issue. When you initialize store you import it:
App.js -> import store from './redux';
store initialization:
a) redux/modules/auth.js -> import api from "../../utils/api"
b) utils/api.js -> import store from '../redux' -> here you get crash
I'd suggest split reducers and action creators into separate files
i used react 16+ and redux get jsonplaceholder fake data to assign posts state but not working. can't assign the state. how can i assign json values into state using concat method. i check lifecycle methods also but can't get the answer.
Reducer
import * as actiontypes from './actions';
import axios from 'axios';
const initalstate = {
counter: 0,
posts: []
};
const reducer = (state = initalstate, action ) => {
switch (action.type) {
case actiontypes.actionFetchPost:
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
return {
...state,
posts: state.posts.concat(res.data)
}
});
break;
default :
return state;
}
};
export default reducer;
Redux reducers must be pure functions, it means they should not contain any side effects like calling api.
You need to call api in action creators using redux-thunk package.
Codesandbox
An example action creator:
import {
FETCH_POSTS_STARTED,
FETCH_POSTS_FAILURE,
FETCH_POSTS_SUCCESS
} from "./actionTypes";
import axios from "axios";
export const fetchPosts = () => {
return dispatch => {
dispatch(fetchPostsStarted());
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then(res => {
dispatch(fetchPostsSuccess(res.data));
})
.catch(err => {
dispatch(fetchPostsFailed(err.message));
});
};
};
const fetchPostsStarted = () => {
return {
type: FETCH_POSTS_STARTED,
payload: {
isLoading: true
}
};
};
const fetchPostsSuccess = posts => {
return {
type: FETCH_POSTS_SUCCESS,
payload: {
posts
}
};
};
const fetchPostsFailed = error => {
return {
type: FETCH_POSTS_FAILURE,
payload: {
error
}
};
};
And reducer file:
import {
FETCH_POSTS_STARTED,
FETCH_POSTS_SUCCESS,
FETCH_POSTS_FAILURE
} from "../actions/actionTypes";
const initialState = {
posts: [],
loading: false,
error: null
};
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS_STARTED:
return {
...state,
loading: true
};
case FETCH_POSTS_SUCCESS:
return {
...state,
loading: false,
error: null,
posts: action.payload.posts
};
case FETCH_POSTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error
};
default:
return state;
}
}
In store we use redux-thunk like this:
import { createStore, compose, applyMiddleware, combineReducers } from "redux";
import reduxThunk from "redux-thunk";
import postsReducers from "./reducers/postsReducers";
const rootReducer = combineReducers({
posts: postsReducers
});
const store = createStore(rootReducer, compose(applyMiddleware(reduxThunk)));
export default store;
Posts component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchPosts } from "./store/actions/postsActions";
class Posts extends Component {
componentDidMount() {
this.props.fetchPosts();
}
render() {
const { posts, loading, error } = this.props;
return (
<div>
{loading && <div>LOADING...</div>}
{error && <div>{error}</div>}
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
}
const mapStateToProps = state => {
const { posts, loading, error } = state.posts;
return {
posts,
loading,
error
};
};
export default connect(
mapStateToProps,
{
fetchPosts
}
)(Posts);
Index.js
import ReactDOM from "react-dom";
import store from "./store/store";
import { Provider } from "react-redux";
import Posts from "./Posts";
function App() {
return (
<div className="App">
<Posts />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Do api call in action and return promise, use redux-thunk and redux-promise-middleware:
export const myApiCall = (args1, arg2) => async (dispatch, getState) => {
const payload = fetch({ ...config });
return dispatch({ type: 'MY_API_CALL', payload });
}
Then in reducer will have to handle two results:
MY_API_CALL_FULFILLED and MY_API_CALL_REJECTED
I am getting this error:
I have this reducer for async:
const initUserState = {
fetching: false,
fetched: false,
users: [],
error: null
};
const userReducer = (state = initUserState, action) => {
switch(action.type){
case "FETCH_USER":
state = {
...state,
users : action.payload
};
break;
case "FETCH_USER_START":
state = {
...state,
fetching: true
};
break;
case "FETCH_USER_SUCCESS":
state = {
...state,
fetched: true,
users: action.payload
};
break;
case "FETCH_USER_ERROR":
state = {
...state,
fetched: false,
error: action.payload
};
break;
default:
break;
}
return state;
};
export default userReducer;
And my container is:
import React from 'react';
import { Blog } from '../components/Blog';
import { connect } from 'react-redux';
//import { act_fetchAllUser } from '../actions/userActions';
import axios from 'axios';
class App extends React.Component {
componentWillMount(){
this.props.fetchAllUser();
}
render(){
console.log("Prop: " , this.props.user);
return(
<div>
<h1>My Blog Posts</h1>
<Blog/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.userReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAllUser: () => {
dispatch((dispatch) => {
dispatch({ type: "FETCH_USER_START" })
axios.get('http://infosys.esy.es/test/get_allBlog.php')
.then((response) => {
dispatch({
type: "FETCH_USER_SUCCESS",
payload: response.data
});
})
.catch((err) => {
dispatch({
type: "FETCH_USER_ERROR",
payload: err
});
})
});
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Im new to redux and I am into async now...
my application should load the users on page load but why my user state is returning undefined state? in the render method, i am logging the user props that contains the returned state from userReducer(mapStateToProps) but i am getting undefined. But when I getState() in store and log it, I am getting the expected result but it is not the ideal place to get the state right?... What i am doing wrong?
My store is:
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import userReducer from './reducers/userReducer';
import thunk from 'redux-thunk';
const store = createStore(userReducer,applyMiddleware(thunk, logger()));
//store.subscribe(() => console.log('State: ',store.getState()));
export default store;
And my index is:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './container/App';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider> , document.getElementById('root'));
Also, on my default case in userReducer, what should I code there.. should I just break; or do I need also to return state it? Thank you
You should have;
const mapStateToProps = (state) => {
return {
user: state.users
};
};
mapStateToProps passes state (global store) as a props to the component.
Global store
const initUserState = {
fetching: false,
fetched: false,
users: [],
error: null
};
inside component listening to user state
const mapStateToProps = (state) => {
return {
user: state.users
};
};