Redux - Combining multiple reducers result in state clearing - reactjs

Fairly new to redux, and have gone through the official guides. Now I'm trying to do something solo. I have two reducers and am using react-thunk. When I dispatch an action after the first one it clears my collection of my other reducer. To illustrate what I mean is I have:
Actions.js
import axios from 'axios';
function fetchAtms() {
return axios.get('http://localhost:4567');
}
export const recievedAtms = (atms) => {
return {
type: 'RECIEVED_ATMS',
atms
}
}
export const completed = () => {
return {
type: 'COMPLETED',
}
}
export const loadMore = () => {
return {
type: 'LOAD_MORE',
}
}
export const loadAtms = (forPerson) => {
return function (dispatch) {
return fetchAtms().then((response) => {
let atms = response.data.map((item) => {return item['location']})
dispatch(recievedAtms(atms));
// When dispatch(completed()); is called
// it is clears my app collection.
dispatch(completed());
// $r.store.getState() => Object {app: {atms: []}, isLoading: false, router: Object}
}, (error) => {
console.log('implement me');
})
}
}
Reducers
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
const app = (state = {}, action) => {
switch (action.type) {
case 'RECIEVED_ATMS':
return {
atms: action.atms
}
default:
return {};
}
}
const isLoading = (state = true, action) => {
switch (action.type) {
case 'COMPLETED':
return !state;
default:
return state;
}
}
const appReducer = combineReducers({
app,
isLoading,
router: routerReducer
});
export default appReducer;
Store.js
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware} from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import thunk from 'redux-thunk';
import appReducer from './reducers/app';
export const history = createHistory()
const middleware = routerMiddleware(history);
const store = createStore(appReducer, applyMiddleware(middleware, thunk));
export default store;
If you hone in on Actions.js where in the loadAtms function I:
Fetch my atms
Dispatch receivedAtms
Dispatch Completed
When I dispatch completed() it clear my atms collection. I'm not entirely sure. I would not expect that since the states between the two reducers are separate. My expectation is:
After I've fired completed() I do not expect it to clear my collection of atms. The resulting state after calling completed() should look like this:
{
isLoading: false,
app: {atms: [{id: 1}, {id: 2}, {id: 3}]}
}
currently what is happening is this:
{isLoading: false, app: {}}
Any thoughts on what I may have done wrong here.

Your atms reducer is returning {} if the action isn't one it is looking for. Instead, you should be returning state I believe. So:
const app = (state = {}, action) => {
switch (action.type) {
case 'RECIEVED_ATMS':
return {
atms: action.atms
}
default:
return state;
}
}

Related

Why redux store only get value of last dispatch Action

I am trying to make multi dispatch action in the action phase of redux:
Here is my code:
export const get_data_time_slot_week = (params) => {
return async (dispatch) => {
dispatch({
type: common.CALL_API_REQUEST,
});
const res = await callAPI.post(TIME_SLOT_WEEK + GET, {
params: { ...params },
});
if (res.status === 200 && res.data.code >= 0) {
//Here, I get new state of Selector timeSlotWeek
dispatch({
type: timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS,
payload: {
data: [...res.data.data],
dataPage: { ...res.data.dataPage },
errCode: res.data.code,
},
});
//And here, I lost state of a Selector timeSlotWeek add get new state of Selector common
dispatch({
type: common.GET_FEEDBACK,
payload: {
msg: "__msg_can_not_to_server",
},
});
}
};
};
Why did it happen? And how can i keep the state of timeSlotWeek with same flow in my code ?
This is my result when i check by Redux tool
GET_DATA_TIME_SLOT_WEEK_SUCCESS => data: { 0: {...}, 1{...} }
GET_FEEDBACK => data: {}
msg: "new msg"
This is my store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
This is my {combineReducers}
import { combineReducers } from "redux";
import feedback from "./feedback.reducer";
import loadingReducer from "./loading.reducer";
import timeSlotWeek from "./timeSlotWeek.reducer";
const rootReducer = combineReducers({
dataTimeSlotWeek: timeSlotWeek,
loading: loadingReducer,
feedback: feedback,
});
export default rootReducer;
Thanks for your help
UPDATE: Problem solve:
Because in my reducer of timeSlotWeek.reducer I have a default case, and when I dispatch another action, this case will run and make the state of timeSlotWeek become initState.
import { common, timeSlotWeek } from "../actions/constants";
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
state = {
// Pass payload to this
};
break;
default:
state = { ...initState };
}
return state;
};
I fix it by this way:
import { common, timeSlotWeek } from "../actions/constants";
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
state = {
// Pass payload to this
};
break;
**case common.CALL_API_FINISH:
state = state;
break;
case common.GET_FEEDBACK:
state = state;
break;**
default:
state = { ...initState };
}
return state;
};
Have any way better than my way ? Thank for cmt
The default reducer case should always return the current state object. I can't think of a single counter-example otherwise (though I have seen some tutorials throw an error here you generally don't want to do that as it adds unnecessary error handling and complicates everything).
You need only define cases for actions your state slice reducer needs to handle, otherwise the let the default case handle it by simply returning the current state.
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
return {
// Pass payload to this
};
default:
return state;
}
};
If you need to reset some state then use another action and case for this, i.e.:
case 'resetTimeSlotWeek':
return initState;

React Redux action is being called before init

I am pretty new to Redux and the whole Redux-Saga thing and wanted to use React-Boilerplate to try a small project that basically just makes an API call and iterates over the data. And I currently have a problem I've been stuck at for hours. Maybe you have an idea?
My React Component looks like this:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { useInjectSaga } from 'utils/injectSaga';
import { useInjectReducer } from 'utils/injectReducer';
import {
makeSelectDevices,
makeSelectLoading,
makeSelectError
} from './selectors';
import reducer from './reducer';
import { fetchDevices } from './actions';
import saga from './saga';
export function LeafletMap(props) {
const {devices, loading, error, fetchDevices } = props;
useInjectReducer({ key: 'leafletMap', reducer });
useInjectSaga({ key: 'leafletMap', saga });
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
if (loading) return(<div>Loading...</div>)
return (
<div>
{ !error ?
<Map center={[47.3, 9.9]} zoom={9} style={{height: '500px'}}>
<TileLayer
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
attribution='© OpenStreetMap contributors'
/>
{ devices && devices.map((device)=> {
let coordinates = [device.latitude, device.longitude];
return (
<Marker key={device.id} position={coordinates}></Marker>
);
})}
</Map>
: ''
}
</div>
);
};
LeafletMap.propTypes = {
devices: PropTypes.array,
loading: PropTypes.bool,
error: PropTypes.any,
};
const mapStateToProps = createStructuredSelector({
devices: makeSelectDevices(),
loading: makeSelectLoading(),
error: makeSelectError(),
});
function mapDispatchToProps(dispatch) {
return {
fetchDevices: () => dispatch(fetchDevices())
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(LeafletMap);
When my component mounts I use the useEffect Hook to dispatch an action that I bound to my props using mapDispatchToProps. The actions file looks like this:
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
CLICK_DEVICE
} from './constants';
export function fetchDevices() {
return {
type: FETCH_DATA,
};
}
export function fetchDevicesSuccess(devices) {
return {
type: FETCH_DATA_SUCCESS,
devices
};
}
export function fetchDevicesError(error) {
return {
type: FETCH_DATA_ERROR,
error
};
}
My saga then reacts to the FETCH_DATA action and calls a generator to fetch the data from my local API:
import { all, call, put, takeEvery } from 'redux-saga/effects';
import request from 'utils/request';
import { fetchDevicesSuccess, fetchDevicesError } from './actions';
import { FETCH_DATA } from './constants';
function* fetchDevicesAsync() {
yield takeEvery(FETCH_DATA, fetchAllDevices);
}
function* fetchAllDevices() {
try {
const requestUrl = '/api/devices';
const devices = yield call(request, requestUrl);
yield put(fetchDevicesSuccess(devices));
} catch (error) {
yield put(fetchDevicesError(error.toString()));
}
}
export default function* rootSaga() {
yield all([fetchDevicesAsync()]);
}
This in return should trigger my reducer which looks as follows:
import produce from 'immer';
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
} from './constants';
export const initialState = {
devices: [],
loading: true,
error: false,
};
/* eslint-disable default-case, no-param-reassign */
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => {
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
case FETCH_DATA_ERROR:
state.loading = false
state.error = action.error;
break;
case FETCH_DATA_SUCCESS:
state.loading = false;
state.error = false;
state.devices = action.devices;
break;
}
});
export default leafletMapReducer;
My problem here is that everything seems to work but my action is neither being displayed in Redux DevTools nor does my component update after the initial render. It seems as if the action is being dispatched before the ##INIT event.
Any idea why this happens?
Thanks in advance!
EDIT:
Just in case it has something to do with my selectors:
import { createSelector } from 'reselect';
import { initialState } from './reducer';
/**
* Direct selector to the leafletMap state domain
*/
const selectLeafletMapDomain = state => state.leafletMap || initialState;
/**
* Other specific selectors
*/
const makeSelectDevices = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.devices
);
const makeSelectLoading = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.loading,
);
const makeSelectError = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.error,
);
/**
* Default selector used by LeafletMap
*/
const makeSelectLeafletMap = () =>
createSelector(selectLeafletMapDomain, leafletMapState => leafletMapState.toJS());
export default makeSelectLeafletMap;
export {
selectLeafletMapDomain,
makeSelectDevices,
makeSelectLoading,
makeSelectError
};
Found the problem myself :)
The problem was in my reducer:
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => { // <-- here
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
I here wrongly mutated my state which leads to the error. The correct solution is:
const leafletMapReducer = (state = initialState, action) =>
produce(state, draftState => { // use draftState instead of normal state
switch (action.type) {
case FETCH_DATA:
draftState.loading = true; //<------
draftState.error = false; //<------
break;

Actions must be plain objects. Use custom middleware for async actions Saga thunk I do have so far in my store

The problem is:
I'm trying to use redux-saga in my react app, but i still has this error: Actions must be plain objects. Use custom middleware for async actions. Code it seems correct but no idea why gives that error. I'll be glad for all the help. I'm fighting with it for about two days and still doesn't have a solution. I tried to look up, but I still have this error.
action...
import { GET_DISTRICTS} from '../../constants';
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
return list;
};
export const actions = {
handleGetDistrictsData: async () => {
let districts = await getAdres(`url is here`);
return {
type: GET_DISTRICTS,
payload: districts
};
},
reducer...
import { GET_DISTRICTS } from '../../constants';
export const initialState = {
districts: [],
quarters: [],
streets: [],
doors: [],
districtSelected: false,
districtSelectedID: null,
quarterSelected: false,
quarterSelectedID: null,
streetSelected: false,
streetSelectedID: null,
doorSelected: false,
doorSelectedID: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_DISTRICTS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
component...
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actions as addressActions } from '../../../../redux/actions/address';
import Select from 'react-select';
const Districts = (props) => {
let [ fetchedData, setFetchedData ] = useState(false);
useEffect(() => {
props.handleGetDistrictsData();
setFetchedData(true);
});
return (
<React.Fragment>
<Select
name='adresSelect'
options={props.address.districts}
onChange={props.handleDistrictChange}
placeholder='Please Select'
/>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
address: state.address
});
const mapDispatchToProps = function(dispatch) {
return bindActionCreators({ ...addressActions }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Districts);
-------------
import React from 'react';
import Districts from './Districts';
const AddressSearchWidget = (props) => {
return (
<React.Fragment>
<Districts />
</React.Fragment>
);
};
export default AddressSearchWidget
store...
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/index';
import * as reducers from './';
export function initStore() {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers(reducers);
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(sagaMiddleware)));
// Run sagas
sagaMiddleware.run(rootSaga);
return store;
}
handleGetDistrictsData returns a promise (all async functions return promises). You cannot dispatch a promise in plain redux saga, and redux-saga does not change this. Instead, dispatch a normal action, and have that action run a saga. The saga can then do async things, and when it's done dispatch another action. The reducer listens only for that second action.
// Actions:
export const getDistrictsData = () => ({
type: GET_DISTRICTS,
})
export const districtsDataSuccess = (districts) => ({
type: DISTRICTS_DATA_SUCCESS,
payload: districts
})
// Sagas:
export function* watchGetDistricts () {
takeEvery(GET_DISTRICTS, getDistricts);
}
function* getDistricts() {
let response = yield fetch(url);
let data = yield response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
yield put(districtsDataSuccess(list));
}
// reducer:
export default (state = initialState, action) => {
switch (action.type) {
case DISTRICTS_DATA_SUCCESS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};

React Redux state not updating

please help me with a situation around react-redux.
I have issues in updating the state ( which i try to do in a immutable way ), and the component where I use it, never rerenders.
/store/users/index.js
import { USER_LOGIN, USER_LOGOUT} from './actionTypes';
import {
USER_LOGIN,
USER_LOGOUT
} from './actionTypes';
const usersReducer = (user = {}, action) => {
switch(action) {
case USER_LOGIN : /* tried to change id manually, not based on payload, just to see if it works */
return {
...user,
name: 'New user',
isLoggedIn: true
}
case USER_LOGOUT:
return {
...user,
name: 'Anonymous',
isLoggedIn: false
}
default:
return user;
}
}
export default usersReducer;
/store/loops/index.js
import {
LOOPS_ADD
} from './actionTypes';
const loopsReducer = (loops =[], action) => {
switch(action) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
export default loopsReducer;
/store/users/actions.js
import {
USER_LOGIN,
USER_LOGOUT
}
from './actionTypes';
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
}
}
export const userLogout = () => {
return {
type: USER_LOGOUT
}
}
/store/index.js
import {
createStore,
combineReducers,
applyMiddleware,
compose
} from 'redux';
/* import reducers */
import usersReducer from './users';
import loopsReducer from './loops';
/* import middleware */
import logger from 'redux-logger';
/* initial state */
const initialState = {
user: {
name: 'Anonymous',
isLoggedIn: false,
email: null,
lastLogin: null,
firstTimeLogin: false,
authProvider: 'email',
friendsCount: null
},
loops: []
}
/* reducers */
const rootReducer = combineReducers({
user: usersReducer,
loops: loopsReducer,
});
/* store creation */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middleware = composeEnhancers(applyMiddleware(logger));
const store = createStore(
rootReducer,
initialState,
middleware
);
export default store;
/pages/HomeScreen/HomeScreen.js
import React from 'react';
import {connect} from 'react-redux'
import {userLogin} from '../../store/users/actions';
class Home extends React.PureComponent {
render() {
return (
<React.Fragment>
<NavBar>Nav</NavBar>
<Listing>
<Filter>Filter</Filter>
<Card>
<CardAvatar>Avatar</CardAvatar>
<CardBody>Rest of the card</CardBody>
Salut {this.props.name}
<button onClick={() => this.props.login()}>login</button>
</Card>
</Listing>
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
return {
name: state.user.name
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(userLogin()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I don't know if it matters but i'm using styled-components for styling.
I tried removing the combineReducers method, and remain with a single reducer, that did not help.
I tried removing the react-logger middleware, I tried using Immutable.jsbut even if the actions is triggered the redux state does not update, and the Home component doesn't rerender as well.
It seems you are not setting the user on your action:
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
}
}
Looks like it needs to be:
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
user: newUser
}
}
Without this there will be no change in state and also no render needed.
Of course, you would then need to change your reducer function so that it is dynamic:
case USER_LOGIN :
return {
...user,
name: action.user.name,
isLoggedIn: true
}
The answer was simple.
I was doing :
const loopsReducer = (loops =[], action) => {
switch(action) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
instead i should have done
const loopsReducer = (loops =[], action) => {
switch(action.type) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
Notice the switch(action) before to switch(action.type)
Sorry for wasting your time, and thank you for all your replies !

react redux reducer unable to set fetch api data to store

i have this code please check below there is this issue that store default state is not able to update using fetch api calling action and reducer as described below
configstore.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import products from './views/Products/reducer';
import categories from './views/Categories/reducer';
import user from './views/Login/reducer';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';
console.log(user);
const defaultState = {
products: {
items: [],
isFetching: 0,
},
categories : {
items: [],
isFetching: 0,
},
user : {
email:'',
token:'',
isLoggedIn:false
}
};
const rootReducer = combineReducers({
products,
categories,
user
});
const store = createStore(
rootReducer,
defaultState,
composeWithDevTools(
applyMiddleware(thunk))
);
export default store;
reducer file contains the switch conditions based on different state keys
import
{ LOGGIN_USER } from './actions';
const isLoggedIn = (state = [], action) => {
switch (action.type) {
case LOGGIN_USER:
return Object.assign( {}, state, { user: action.payload.isLoggedIn } );
default:
return state;
}
};
const email = (state = [], action) => {
switch (action.type) {
case LOGGIN_USER:
return Object.assign( {}, state, { user: action.payload.email } );
default:
return state;
}
};
const token = (state = [], action) => {
switch (action.type) {
case LOGGIN_USER:
return Object.assign( {}, state, { user: action.payload.token } );
default:
return state;
}
};
export default combineReducers({
email,
token,
isLoggedIn
});
actions file
import 'whatwg-fetch';
import config from '../../config/config';
export const REQUEST_PRODUCTS = 'REQUEST_PRODUCTS';
export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS';
export const LOGGIN_USER = 'LOGGIN_USER';
// export const loggOnUser = products => ({
// type: LOGGIN_USER,
// products,
// });
export const authenticateUser = ( auser ) => ({
type: LOGGIN_USER,
auser
});
please anyone needed help on this why store state with key
user{
'email':''
'token':'',
'isLoggedIn':''
}
any help would be highly appreciated thanks in advance

Resources