state are being updated before reducer is update the state - javascript

I'm using userReducer to manage user state across the app but while updating the state using reducer the state is updates before reducer is able to update it.
Inspect
here you can see that the previous state variable is updated to new value in payload.
store.js
import { compose, applyMiddleware } from "redux";
import { legacy_createStore as createStore } from "redux";
import { logger } from "redux-logger";
import { rootReducer } from "./rootReducer";
export const store = createStore(rootReducer,applyMiddleware(logger));
rootReducer.js
import { combineReducers } from "redux";
import { postReducer } from "./posts/posts.reducer";
import { userReducer } from "./user/user.reducer";
export const rootReducer = combineReducers({
post: postReducer,
user:userReducer
});
userReducer
import { User_Action_Types } from "./user.types";
const INITIAL_STATE = {
data: {
_id: "",
name: "",
email: "",
password: "",
},
};
export const userReducer = (state = INITIAL_STATE, action) => {
const { type, payload } = action;
console.log({ action, state });
switch (type) {
case User_Action_Types.SetUser: {
state.data = payload;
return state;
}
case User_Action_Types.ResetUser: {
return INITIAL_STATE;
}
default:
return state;
}
};
I tried to change actions then reinstalled the modules but nothing worked.
Please help to fix the issue.

current your mutating state return new state in reducer
case User_Action_Types.SetUser: {
return {
...state,
data: payload
};
}

Related

ReactJS & Redux - TypeError: state.reduxCart is not iterable

I am trying to insert items in redux but when i click the button I get the following error :
TypeError: state.reduxCart is not iterable
My Reducer code :
const INITIAL_STATE = {
reduxCart: [],
reduxCartCounter: 0
}
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADDITEM':
return {
...state,
reduxCart: [action.payload , ...state.reduxCart] // ERROR
}
case 'ADDCOUNTER':
return ({
...state,
reduxCartCounter: action.payload
})
default:
return state;
}
}
Here is my Store code :
import rootReducer from './reducer';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);
export { store, persistor };
My Actions Code :
export function AddToReduxCart(item) {
return dispatch => {
dispatch({ type: 'ADDITEM', payload: item })
}
}
How to solve this issue ? Any help would be appreciated.
On your actions file, in your addItem function you can use getState function. then concat the two of the arrays in your action and dispatch them as the payload.
should be something like this:
import store from '../store';
export function addItem(nextItem) {
const currentReduxCart = store.getState().reduxCart.map(item => ({...item}));
const payload = [nextItem , ...currentReduxCart];
return {
type: 'ADDITEM',
payload: payload,
}
}
const INITIAL_STATE = {
reduxCart: [],
reduxCartCounter: 0
}
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADDITEM':
return {
...state,
reduxCart: action.payload
}
case 'ADDCOUNTER':
return ({
...state,
reduxCartCounter: action.payload
})
default:
return state;
}
}
If you want to add your actions code I will be able to help You to adjust it for your code.

State property has empty object instead of actual value set by action

I'm fetching data via API and want to show error message if the request fails. I'm dispatching setDuplicatesError that should set state.error property to error message. This is how my reducer looks like:
export function setDuplicatesPending(loading) {
return {
type: 'FETCH_DUPLICATES_PENDING',
loading
}
}
export function setDuplicates(duplicates) {
return {
type: 'FETCH_DUPLICATES_SUCCESS',
duplicates
};
}
export function setDuplicatesError(error) {
return {
type: 'FETCH_DUPLICATES_FAILURE',
error
};
}
export default function duplicatesData(state = {loading: true}, action) {
switch (action.type) {
case 'FETCH_DUPLICATES_FAILURE':
console.log("Failure action is dispatched.", action.error);
return {...state, error: action.error};
case 'FETCH_DUPLICATES_PENDING':
console.log("Loading action is dispatched.")
return {...state, loading: action.loading };
case 'FETCH_DUPLICATES_SUCCESS':
return {...state, rows: action.duplicates, loading: false };
default:
return state;
}
}
action.error has the actual error message which I can see in console:
Failure action is dispatched. TypeError: Failed to execute 'fetch' on 'Window': Request cannot be constructed from a URL...
but when I try displaying it in my component
render() {
return (<div> {JSON.stringify(this.props.state)}</div>)
}
I'm getting the following:
{"duplicatesData":{"error":{}}}
Loading action works fine and shows true/false in state when I display it.
What's causing this behavior?
This is how I create store:
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from 'redux-thunk';
import duplicatesData from "./myReducer";
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const store = createStore(
combineReducers({
duplicatesData
}),
composeEnhancer(applyMiddleware(thunk))
);
store.asyncReducers = {};
return store;
};
and link it it to the component:
function mapStateToProps(state) {
return {
state
};
}
function mapDispatchToProps(dispatch) {
return {
fetchDuplicates: () => dispatch(fetchDuplicates())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DuplicatesTable);
if loading actions works fine you can forget to connect your reducers to the store
an example of the store configuring
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunkMiddleware from 'redux-thunk';
import duplicatesData from './duplicatesData';
const reducers = combineReducers({
duplicatesData
});
const initialState = {};
createStore(
reducers,
initialState,
compose(applyMiddleware(thunkMiddleware))
);

State not updating with react redux thunk

I'm a bit new to using redux and react. I'm trying to make a simple API call with redux and having it render in react. I can see the API call working as it's in the payload in redux dev tools, but I can't seem to get it to update the state possibly in the `connect?.
actions/index
import FilmAPI from '../api/api';
export const FETCH_FILMS = 'FETCH_FILMS';
export const RECEIVE_FILMS = 'RECEIVE_FILMS';
export const receiveFilms = (films) => {
return {
type: RECEIVE_FILMS,
films
};
}
export const fetchFilmsRequest = () => {
return dispatch => {
return axios.get('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => {
dispatch(receiveFilms(response.data))
})
}
}
export default fetchFilmsRequest;
reducers/FilmReducer
import RECEIVE_FILMS from '../actions/index';
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return [...state, action.films];
default:
return state;
}
}
reducers/index
import { combineReducers } from 'redux';
import { films } from './FilmsReducer';
export default combineReducers({
films,
});
containers/FilmListContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchFilmsRequest } from '../actions';
import FilmList from '../components/FilmList'
class FilmListContainer extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchFilmsRequest();
}
render() {
return (
<div>
<FilmList films={this.props.films}/>
</div>
);
}
}
const mapStateToProps = state => ({
films: state.films
})
export default connect(mapStateToProps, {fetchFilmsRequest: fetchFilmsRequest})(FilmListContainer);
configureStore.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// options like actionSanitizer, stateSanitizer
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk)
);
return createStore(
rootReducer,
initialState,
enhancer
);
}
As mentioned, Redux DevTools show the films in the payload, but films still remain 0 in its state. Could anyone please point me in the right direction?
You can get updated state by subscribing store and use store.getState()
Steps:
Write subscribe function in constructor of component class.
Set state of class by store.getState().
import store from '../js/store/index';
class MyClass extends Component {
constructor(props, context) {
super(props, context);
this.state = {
classVariable: ''
}
store.subscribe(() => {
this.setState({
classVariable: store.getState().MyStoreState.storeVariable
});
});
}
}
You are close, your action needs to send the data to the store by dispatching an event which your reducer can then catch. This is done using the type attribute on the dispatch object.
https://redux.js.org/basics/actions
return fetch('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => {
dispatch({
type: RECEIVE_FILMS,
payload: response,
})
})
You then need to grab the data in your reducer and put it in the store
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return {
...state,
films: action.payload.films
};
default:
return state;
}
}
It looks like you just need to import your action type constant into your reducer using a named import instead of a default export.
i.e. import {RECEIVE_FILMS} from '../actions' rather than import RECEIVE_FILMS from '../actions'
Just dispatch result of resolved fetch promise like so:
if the payload is json, then:
export const fetchFilmsRequest = () => {
return dispatch => {
return fetch('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => response.json())
.then(response => {
dispatch({
type: RECEIVE_FILMS,
payload: response
})
})
}
Your reducer would need modifying slightly to:
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return [...action.payload]; // assuming response is jus array of films
default:
return state;
}
}

React + Redux binded state has no data

After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )

Adding a 'delete from cart' action to my redux store

I've been trying to add a 'delete from cart' action to my redux setup. So far I can add items to a whitelist I have set up in my store but I'm not sure on how to delete items from the cart. This is my store:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import reducer from './reducers';
import thunkMiddleware from 'redux-thunk';
import {createLogger} from 'redux-logger';
const store = createStore(
reducer,
undefined,
compose(
applyMiddleware(createLogger(), thunkMiddleware),
autoRehydrate()
)
);
persistStore(store, {whitelist: ['cart']});
export default store;
These are my reducers:
import {ADD_CART} from './actions';
import { REHYDRATE } from 'redux-persist/constants';
export default Reducer;
var initialState = {
cart:{},
data: [],
url: "/api/comments",
pollInterval: 2000
};
function Reducer(state = initialState, action){
switch(action.type){
case REHYDRATE:
if (action.payload && action.payload.cart) {
return { ...state, ...action.payload.cart };
}
return state;
case ADD_CART:
return {
...state,
cart: [...state.cart, action.payload]
}
default:
return state;
};
}
And these are my actions:
export const ADD_CART = 'ADD_CART';
export function addCart(item){
return {
type: ADD_CART,
payload: item
}
};
export function removeCart(item){
return{
type: REMOVE_CART,
payload: item
}
};
In my Cart component is where I want to have a delete from cart button where you can choose a specific item to delete:
import React, { Component } from 'react';
import {addCart} from './Shop';
import { connect } from 'react-redux';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
}
...
render() {
return(
<div className= "Webcart" id="Webcart">
{this.countTotal()}
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
}
}
function mapStateToProps(state) {
return { cart: state.cart };
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
I want to be able to select an specific item from the cart array and delete it. I believe this should be done through an action with redux since my array is being saved in my store. How can I do this?
You are right, just dispatch the removeCart action (which you have already defined) from your component:
const mapDispatchToProps = (dispatch) => {
return {
onCartRemove: (cart) => {
dispatch(removeCart(cart));
},
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
}
}
and update the state by handle it in your reducer
case REMOVE_CART:
return {
...state,
cart: state.cart.filter((item) => payload !== item)
}

Categories