Hello so I have added redux to the project(Ecom site) that I am working on, so I am fetching products with the help of Redux, I just then added another redux Action for when the user clicks on the product to view the details so it works and I do get back the results that I expect but now the problem is when I page route to a page which has redux I get an error until I reload the page and everything works perfectly
Code to my store
import { combineReducers, applyMiddleware, compose, createStore } from "redux";
import thunk from "redux-thunk";
import { HoodiesFecthReducer } from "./Reducers/ProductsFecthReducer/HoodiesFetchReducer";
import { JacketsFetchReducer } from "./Reducers/ProductsFecthReducer/JacketsFecthReducer";
import productDetailsFetchReducer from "./Reducers/ProductsFecthReducer /ProductDetailsFetchReducer";
import { T_ShirtsFetchReducer } from "./Reducers/ProductsFecthReducer/T-ShirtFetchReducer";
const initialState = {};
const reducers = combineReducers({
Hoodies: HoodiesFecthReducer,
Jackets: JacketsFetchReducer,
T_Shirts: T_ShirtsFetchReducer,
ProductDetail: productDetailsFetchReducer,
});
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export const store = createStore(
reducers,
initialState,
composeEnhancer(applyMiddleware(thunk))
);
Code below is one of the action that fetches the product so I will add in one because they are all the same I avoid adding tons of code that are performing same task
import {
PRODUCT_FETCH_FAIL,
PRODUCT_FETCH_REQUEST,
PRODUCT_FETCH_SUCCESS,
} from "./actionTypes";
import axios from "axios";
export const JacketsFetchAction = () => async (dispatch) => {
const url = "http://127.0.0.1:5000/api/clothes/jackets";
try {
dispatch({ type: PRODUCT_FETCH_REQUEST });
const { data } = await axios.get(url);
dispatch({ type: PRODUCT_FETCH_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_FETCH_FAIL, payload: error });
}
};
So now things started breaking when I added this action to the mix
import {
PRODUCT_FETCH_FAIL,
PRODUCT_FETCH_REQUEST,
PRODUCT_FETCH_SUCCESS,
} from "./actionTypes";
import axios from "axios";
const productDetailFetchAction = (productID, productCategory) => async (
dispatch
) => {
const url = `http://127.0.0.1:5000/api/clothes/product-detail/${productCategory} /${productID}`;
try {
dispatch({ type: PRODUCT_FETCH_REQUEST });
const { data } = await axios.get(url);
dispatch({ type: PRODUCT_FETCH_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_FETCH_FAIL, payload: error });
}
};
export default productDetailFetchAction;
Related
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
Cant use Redux thunk show list products
import axios from "axios";
import {
ALL_PRODUCT_FAIL,
ALL_PRODUCT_REQUEST,
ALL_PRODUCT_SUCCESS,
CLEAR_ERRORS,
} from "../constants/productsConstants";
// Get All Products
export const getProduct = () =>
async (dispatch) => {
try {
dispatch({ type: ALL_PRODUCT_REQUEST });
let link = `http://localhost:8080/api/v1/products/`;
const { data } = await axios.get(link);
dispatch({
type: ALL_PRODUCT_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ALL_PRODUCT_FAIL,
// payload: error.response.data,
});
}
};
// Clearing Errors
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
export const ALL_PRODUCT_REQUEST = "ALL_PRODUCT_REQUEST";
export const ALL_PRODUCT_SUCCESS = "ALL_PRODUCT_SUCCESS";
export const ALL_PRODUCT_FAIL = "ALL_PRODUCT_FAIL";
export const CLEAR_ERRORS = "CLEAR_ERRORS";
import {
ALL_PRODUCT_REQUEST,
ALL_PRODUCT_SUCCESS,
CLEAR_ERRORS,
} from "../constants/productsConstants";
export const productsReducer = (state = { products: [] }, action) => {
switch (action.type) {
case ALL_PRODUCT_SUCCESS:
return {
loading: true,
products: [],
};
case ALL_PRODUCT_REQUEST:
return {
loading: false,
products: action.payload.products,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
// Clearing Errors
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import {
productsReducer,
} from "./reducers/productsReducer";
const reducer = combineReducers({
products: productsReducer,
});
// let initialState = {};
const middleware = [thunk];
const store = createStore(
reducer,
// initialState,
{},
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
import { React, useEffect } from "react";
import Layout from "../../components/Layouts/Layout"
import Link from "next/link";
import { useDispatch, useSelector } from "react-redux"
import { getProduct, clearErrors } from "../../redux/actions/productsAction"
const ProductList = () => {
const dispatch = useDispatch();
const { products, error } = useSelector((state) => state.products);
useEffect(() => {
if (error) {
dispatch(clearErrors());
}
dispatch(getProduct())
console.log("products: ", products)
}, [dispatch, error]);
return (
)
}
export default ProductList
Im new to react and redux. After following a tutorial and doing everything wrong (he was using react-router-dom 5.2 and I was using 6) I decided to do it like him and use react-router-dom 5.2. Now for some reason the redux part of the project doesn't work. I don't even get an error. It just doesn't show in the redux dev tools. I think the frontend isn't connecting to the backend properly (Django). Maybe someone has an idea?
Homescreen:
import React, { useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Row, Col } from 'react-bootstrap'
import Product from '../components/Product'
import Loader from '../components/Loader'
import Message from '../components/Message'
import { listProducts } from '../actions/productActions'
function HomeScreen() {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const { error, loading, products} = productList
//let keyword = history.location.search
useEffect(() => {
dispatch(listProducts())
}, [dispatch])
productReducers.js
import {
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from '../constants/productConstants'
export const productListReducer = (state = { products: [] }, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return { loading: true, products: [] }
case PRODUCT_LIST_SUCCESS:
return {
loading: false,
products: action.payload
}
case PRODUCT_LIST_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
productActions.js
import axios from 'axios'
import {
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from '../constants/productConstants'
export const listProducts = () => async (dispatch) => {
try {
dispatch({ type: PRODUCT_LIST_REQUEST })
const { data } = await axios.get('/api/products/')
dispatch({
type: PRODUCT_LIST_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: PRODUCT_LIST_FAIL,
payload: error.response && error.response.data.detail
? error.response.data.detail
: error.message,
})
}
}
store.js
import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import {productListReducer} from './reducers/productReducers'
const reducer = combineReducers({
productList: productListReducer,
})
const initialState = {}
const middleware = [thunk]
const store = createStore(reducer, initialState,
composeWithDevTools(applyMiddleware(...middleware)))
export default store
My redux dev tool in browser is empty and there are no errors on the console. Thank you!
created a state using redux-toolkit but right after i refresh page or go to another one the state resets. i've been broking my head for 4 hours, can't stand it anymore.
there is code on github gists
Also here it is:
index.ts file: configuration of redux store
import { configureStore } from "#reduxjs/toolkit";
import authStateSlice from "redux/authStateSlice";
import deviceTypeReducer from "redux/deviceTypeSlice";
import userDataSlice from "redux/userDataSlice";
const store = configureStore({
reducer: {
isMobile: deviceTypeReducer,
isLogin: authStateSlice,
userData: userDataSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
signin.tsx: there i fetch data from a server and put it in my redux state:
import React, { useEffect, useState } from "react";
import { useAppSelector } from "lib";
import { useAppDispatch } from "../../lib/useAppDispatch";
import { userActionState } from "redux/userDataSlice";
export const SignIn: React.FC = () => {
const { isUser } = useAppSelector((state) => state.userData);
console.log("IS USER: ", isUser);
const dispatch = useAppDispatch();
const loginHandler = async () => {
try {
const response = await fetch(
"...link to api",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
}
);
if (!response.ok) {
const err = await response.json();
throw new Error(err)
}
const data = await response.json();
dispatch(userActionState({ isUser: true }));
} catch (error) {
throw new Error(err)
}
};
return (
<div>
...
</div>
);
};
userDataSlice.tsx: my user's data slice:
import { createSlice } from "#reduxjs/toolkit";
import { ResponseLoginDataType } from "#types";
const initialState: {
isUser: boolean | undefined;
} = {
isUser: undefined,
};
const userDataSlice = createSlice({
name: "isUser",
initialState,
reducers: {
userActionState(
state,
action: {
payload: {
isUser: boolean | undefined;
};
}
) {
state.isUser = action.payload.isUser;
},
},
});
export default userDataSlice.reducer;
export const { userActionState } = userDataSlice.actions;
userInfo.tsx: there i tried to get that state, but it gives my default one:
import React, { useEffect, useState } from "react";
import { useAppSelector } from "lib";
export const UserInfo: React.FC<UserInfo> = ({
name,
nickname,
picture,
activity,
active,
}) => {
const { isUser } = useAppSelector((state) => state.userData);
console.log("USER:: ", isUser);
return (
<>
.....
</>
);
};
Your Redux state doesn't persist to any non-volatile storage, such as local storage or cookies. redux-persist could be an option for you to keep your application's state.
This blog post might help: https://blog.logrocket.com/persist-state-redux-persist-redux-toolkit-react/
When you refresh or leave page, you are restarting the redux store so it will always go back to the default. If you want to persist the store you will need to use a tool like redux-persist. Here is how you could setup your store with redux-persist.
One thing that is annoying is making sure your redux store persists nested objects, secondly since you are in typescript don't try to persist the root reducer.
import storage from "redux-persist/lib/storage";
import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
import persistReducer from "redux-persist/es/persistReducer";
import persistStore from "redux-persist/es/persistStore";
const persistConfigOne = {
key: "ex1",
storage,
stateReconciler: autoMergeLevel1, // Shallow level persisted objects
};
const persistConfigTwo = {
key: "ex2",
storage,
stateReconciler: autoMergeLevel2, // This will persist deeper nested objects if needed.
};
//Can't get state typings if persisting root reducer. Persisted by reducer works.
const reducers = combineReducers({
ex1: persistReducer<Ex1State, any>(persistConfigOne, ex1Reducer),
ex2: persistReducer<Ex2State, any>(persistConfigTwo, ex2Reducer),
});
export const store = configureStore({
reducer: reducers,
devTools: process.env.NODE_ENV !== "production", // May want to add this for Redux Dev Tools
middleware: [thunk], // Probably will need to use thunk at some point
});
I am making a React-redux component to embed in Laravel blade file. Where in the react side,
I am using redux with the thunk, When I try to get data without thunk from the Laravel route, it getting properly.
But When I use an axios request in the action creator to get data asynchronously. It gives the:
'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.'
This is the entry component of the react side.
Entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('like_post'));
This is the App.js main component
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';
class App extends Component {
constructor(props) {
super(props)
var domain = window.location.hostname;
var url = window.location.pathname;
var urlsplit = url.split("/");
var post_slug = urlsplit[urlsplit.length - 1];
this.state={
post_slug: post_slug,
post_likes:''
}
}
kFormatter(num) {
return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
}
componentDidMount() {
this.props.getCurrentPostLikes();
// axios.get(`/api/get_post_likes/${this.state.post_slug}`)
// .then(response => {
// console.log(response.data.likes);
// this.setState({
// post_likes: response.data.likes
// })
// })
}
render() {
return (
<div className="App">
<img src="/images/love.svg" alt="post like" width="50px" height="50px"/>
<p>{this.kFormatter(this.state.post_likes)}</p>
<p><span>{this.props.likes}</span></p>
</div>
);
}
}
export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);
This is the actions.js file /store/actions/actions.js
// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';
import axios from 'axios';
import {GET_POST_LIKES} from './types';
// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
return dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
}
Tried this action creator also, but still same error
export const getCurrentPostLikes = () => {
return dispatch => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}
}
This is the reducers.js file under /store/reducers/reducer.js
import { GET_POST_LIKES } from '../actions/types';
const initialState = {
likes: null
};
const reducer = (state=initialState, action) => {
const newState = {...state};
switch(action.type){
case 'GET_POST_LIKES':
return {
...state,
post: action.payload
}
case 'LIKE_UP':
newState.likes += action.value
break;
}
return newState;
};
export default reducer;
Now, This should return a field value of posts table, with post id = 2.
Your problem is at Entry.js, this line:
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
You are setting the thunk middleware as the second parameter. The second parameter is for the initial state.
The thunk middleware should be part of the composed enhancers in the third parameter of the createStore function.
The parameters should be applied as such:
createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
Full example:
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
export default createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
This is the 'signature' of an action creator curried function using redux-thunk:
export const getCurrentPostLikes = () => async dispatch => {
const response = await axios.get(`/api/get_post_likes/2`);
dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
This is how you want your action creator to look:
export const getCurrentPosts = () => {
return async function(dispatch, getState) {
const response = await axios.get("/api/get_post_likes");
dispatch({ type: "GET_POST_LIKES", payload: response });
};
};
Leave the Promises out of it and go with ES7 async/await syntax and also leave out the setTimeout, unless you can provide a really good justification for wanting to time out an asynchronous request that is already taking a non-zero amount of time to complete, maybe I don't understand, but you do want that data don't you? It's necessary for your reducers to do their jobs and eventually update state in your application.
What I have above is not perfect in that it's boilerplate, like you don't really need getState in there, yes it's part of middleware, but if you are not going to use something, no need to define it. Anyway I am trying to get you to a point where your action creator works again and you are sparing your eyes and your mind.
It seems your action has an extra dispatch =>
export const getCurrentPostLikes = () => dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
I'm getting this error every time I try to dispatch my action:
Error: Actions must be plain objects. Use custom middleware for async
actions.
I've installed redux-thunk and without async actions, it's working.
Store config:
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducers from '../reducers/index';
const logger = createLogger();
export default createStore(reducers, composeWithDevTools(applyMiddleware(thunk)));
UI:
...
import { connect } from 'react-redux';
import { getCities } from '../../actions/cities';
...
componentDidMount = async () => {
try {
const cities = await this.props.getCities();
console.log(cities);
} catch (error) {
console.log(`errorhome: ${error}`);
}
SplashScreen.hide();
}
...
const mapDispatchToProps = dispatch => ({
changeNetworkStatus: () => dispatch(changeNetworkStatus),
getCities: () => dispatch(getCities),
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Action:
import database from '../config/utils';
export const GET_CITIES_START = 'GET_CITIES_START';
export const GET_CITIES_FINISHED = 'GET_CITIES_FINISHED';
export const GET_CITIES_ERROR = 'GET_CITIES_ERROR';
const getCititesStart = () => ({ type: GET_CITIES_START });
const getCititesFinished = cities => ({ type: GET_CITIES_FINISHED, cities });
const getCititesError = error => ({ type: GET_CITIES_ERROR, error });
export const getCitites = () => async (dispatch) => {
dispatch(getCititesStart());
try {
const cities = [];
const snap = await database.ref('cities').once('value');
console.log('snap: ', snap);
snap.forEach((element) => {
const city = {
city: element.key,
coordinate: element.val().coordinate,
};
cities.push(city);
});
dispatch(getCititesFinished(cities));
} catch (error) {
dispatch(getCititesError(error));
}
};
EDIT: If I add logger to middlewares too, the error message is this:
TypeError: Cannot read property 'type' of undefined
Thanks for your help!
Actions are functions that return a object with action's data, that data is a object with a type property.
You're dispatching action like this:
dispatch(getCities)
You should dispatch action like this:
dispatch(getCities())