Why redux store only get value of last dispatch Action - reactjs

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;

Related

How to reset state of Redux Store when using configureStore from #reduxjs/toolkit?

I have seen solutions for clearing/resetting the store after logout but did not understand how to implement the same functionality for the following way of setting up the redux store.
Store.js:
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import authReducer from './ducks/authentication'
import snackbar from './ducks/snackbar'
import sidebar from './ducks/sidebar'
import global from './ducks/global'
import quickView from './ducks/quickView'
import profileView from './ducks/profileView'
const store = configureStore({
reducer: {
auth: authReducer,
snackbar,
sidebar,
global,
quickView,
profileView,
},
middleware: [...getDefaultMiddleware()],
})
export default store
Here is how all the reducers implemented using createAction and createReducer from #reduxjs/toolkit.
snackbar.js:
import { createAction, createReducer } from '#reduxjs/toolkit'
export const handleSnackbar = createAction('snackbar/handleSnackbar')
export const openSnackBar = (
verticalPosition,
horizontalPosition,
message,
messageType,
autoHideDuration = 10000
) => {
return async dispatch => {
dispatch(
handleSnackbar({
verticalPosition,
horizontalPosition,
message,
autoHideDuration,
messageType,
isOpen: true,
})
)
}
}
export const closeSnackbar = () => {
return dispatch => {
dispatch(handleSnackbar({ isOpen: false }))
}
}
const initialState = {
verticalPosition: 'bottom',
horizontalPosition: 'center',
message: '',
autoHideDuration: 6000,
isOpen: false,
messageType: 'success',
}
export default createReducer(initialState, {
[handleSnackbar]: (state, action) => {
const {
isOpen,
verticalPosition,
horizontalPosition,
message,
autoHideDuration,
messageType,
} = action.payload
state.isOpen = isOpen
state.verticalPosition = verticalPosition
state.horizontalPosition = horizontalPosition
state.message = message
state.autoHideDuration = autoHideDuration
state.messageType = messageType
},
})
As per Dan Abramov's answer, create a root reducer which will simply delegate the action to your main or combined reducer. And whenever this root reducer receives a reset type of action, it resets the state.
Example:
const combinedReducer = combineReducers({
first: firstReducer,
second: secondReducer,
// ... all your app's reducers
})
const rootReducer = (state, action) => {
if (action.type === 'RESET') {
state = undefined
}
return combinedReducer(state, action)
}
So, if you have configured your store with #reduxjs/toolkit's configureStore, it might look like this:
import { configureStore } from '#reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export default configureStore({
reducer: {
counter: counterReducer,
// ... more reducers
},
});
where configureStore's first parameter reducer accepts a function (which is treated as root reducer) or an object of slice reducers which is internally converted to root reducer using combineReducers.
So, now instead of passing object of slice reducers (shown above), we can create and pass root reducer by ourselves, here is how we can do it:
const combinedReducer = combineReducers({
counter: counterReducer,
// ... more reducers
});
Now, lets create a root reducer which does our reset job when needed:
const rootReducer = (state, action) => {
if (action.type === 'counter/logout') { // check for action type
state = undefined;
}
return combinedReducer(state, action);
};
export default configureStore({
reducer: rootReducer,
middleware: [...getDefaultMiddleware()]
});
Here is CodeSandbox
I wanted to extend Ajeet's answer so that it is accessible to those who want complete type safety throughout their Redux store.
The key differences are that you need to declare a RootState type, which is documented in the RTK docs
const combinedReducer = combineReducers({
counter: counterReducer
});
export type RootState = ReturnType<typeof combinedReducer>;
And then in your rootReducer, where you are executing your logout function, you want to maintain type safety all the way down by giving the state param the RootState type, and action param AnyAction.
The final piece of the puzzle is setting your state to an empty object of type RootState instead of undefined.
const rootReducer: Reducer = (state: RootState, action: AnyAction) => {
if (action.type === "counter/logout") {
state = {} as RootState;
}
return combinedReducer(state, action);
};
I forked Ajeet's answer on CodeSandbox, added the required types, and you can view it here.
If you're looking to reset each slice to its initial state (unlike setting the entire state to an empty object) you can use extraReducers to respond to a logout action and return the initial state.
In auth.tsx:
const logout = createAction('auth/logout')
In foo.tsx:
const initialState = {
bar: false,
}
const fooSlice = createSlice({
name: 'foo',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(logout, () => {
return initialState
})
},
})
A simplified example with two reducers:
// actions and reducer for state.first
const resetFirst = () => ({ type: 'FIRST/RESET' });
const firstReducer = (state = initialState, action) => {
switch (action.type) {
// other action types here
case 'FIRST/RESET':
return initialState;
default:
return state;
}
};
// actions and reducer for state.second
const resetSecond = () => ({ type: 'SECOND/RESET' });
const secondReducer = (state = initialState, action) => {
switch (action.type) {
// other action types here
case 'SECOND/RESET':
return initialState;
default:
return state;
}
};
const rootReducer = combineReducers({
first: firstReducer,
second: secondReducer
});
// thunk action to do global logout
const logout = () => (dispatch) => {
// do other logout stuff here, for example logging out user with backend, etc..
dispatch(resetFirst());
dispatch(resetSecond());
// Let every one of your reducers reset here.
};
The simple solution - just add a reducer like this...
resetList: (state) => {
return (state = []);
},
... and call it with a button:
const handleResetList = () => {
dispatch(resetList());
};
return (
<div>
<div>List</div>
<button onClick={handleResetList}>Reset</button>

How to fetch data from server with redux?

I need to fetch data from server with the help of such instrument as Redux. I watched some tutorials about it and wrote some code for it. Here it is:
actions/fetching_actions.js
import * as Actions from '../constants/action_types';
function fetchListOfCities() {
return fetch(`${Actions.BASE_URL}/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d`);
}
export const listOfCitiesRequest = () => function (dispatch) {
return fetchListOfCities()
.then(list => list.json())
.then((list) => {
dispatch(getListOfCities(list));
}).catch((error) => {
console.log(error);
});
};
export const getListOfCities = result => ({
type: Actions.LIST_RESPONSE,
result,
});
constants/action_types.js
export const BASE_URL = 'http://api.openweathermap.org';
export const LIST_RESPONSE = 'LIST_RESPONSE';
export const CITY_RESPONSE = 'CITY_RESPONSE';
reducers/fetching_reducer.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
reducers/index.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
And unfortunately I don't know what should I do further. Before I fetched data in this way in Component:
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
this.setState({ data });
}
};
But I heard that it's better to fetch data by redux, so, please, can you explain me how to finish this fetching part, what should I add here?
In saga please import these things
import { put } from 'redux-saga/effects';
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
yield put({ type: LIST_RESPONSE, payload: data });
}
};
In reducer
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.payload,
};
To send some request to the server via redux you should use one of middlewares:
redux-thunk
redux-promise
redux-saga
redux-observable
etc.
The easiest I think is redux-thunk.
1. Install the package:
$ npm install redux-thunk
2. Connect it to your store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
After that, you will be allowed to dispatch to redux not only a plain javascript object but also functions.
3. So you can dispatch like this:
store.dispatch(listOfCitiesRequest());

Modifying state from another reducer in react

I'm pretty new in react so this might be a silly question.
I'm working on an app that manage rss feeds, so the structure of my entire app is similar to this one
<div className="App">
<Header />
<Feeds />
</div>
both components have their own reducer and actions.
the problem appears when I'm trying to create a new feed (actually managed in the feeds reducer) from my header component. so I have to access to the state of the feedsReducer from my headerReducer.
I'm not sure how to proceed at this point.
should I access the feeds reducer from the header component? ( this also implies that the feedsReducer needs to know my header actions)
I'll add some code to make the problem clear
index.js
import feedsReducer from './components/Feeds/FeedsReducer';
import headerReducer from './components/Header/HeaderReducer';
const rootReducer = {
feeds:feedsReducer,
header: headerReducer
};
const store = createStore(combineReducers(rootReducer));
Header/Header.js
import { ADD_FEED } from './actions';
class Header extends Component {
state = {
feedUrl: ""
};
addFeed = () => {
axios.post(
'/feeds/add',
{
url: 'myNewRssFeed.com'
})
.then(feed => {
//this is calling the HeaderReducer
this.props.addFeed(feed.data);
})
.catch(err => console.log(err));
}
}
const mapDispatchToProps = dispatch => {
return {
addFeed: (feed) => dispatch({ type: ADD_FEED, payload: { feed } })
};
};
export default connect(null, mapDispatchToProps)(Header);
Header/actions.js
export const ADD_FEED = "ADD_FEED";
HeaderComponent/HeaderReducer.js
const reducer = (state, action) => {
const newState = {
...state
}
switch (action.type) {
case storeActions.ADD_FEED:
// at this point newState.feeds doesn't exist because it's part from the FeedsReducer
newState.feeds = newState.feeds.push(action.payload.feed);
break;
}
return newState;
}
Feeds/FeedsReducer.js
const initialState = {
feeds: []
}
const reducer = (state = initialState, action) => {
const newState = {
...state
}
switch (action.type) {
//this action is commented because was recently moved to the headerComponent/actions.js
/* case storeActions.ADD_FEED:
newState.feeds = newState.feeds.push(action.payload.feed);
break; */
case storeActions.LOAD_FEEDS:
newState.feeds = action.payload.feeds;
break;
}
return newState;
}
Thanks in advice.
I don't really think you need to access reducer in any way. Reducer function will update store based on action it's listenning to.
Here is an example:
import * as constants from 'constantpathhere';
export function feedReducer(state = INITIAL_STATE, action) {
const { type, payload } = action;
switch(type) {
case constants.ADD_FEED: // listen to ADD_FEED action
return {... state, data: payload };
case constants.LOAD_FEEDS: // listen to LOAD_FEEDS
return {...state, loading: true }
...
default:
return state;
}
}
export function headReducer(state = INITIAL_STATE, action) {
const { type, payload } = action;
switch(type) {
case constants.ANY_ACTION: // listen to ADD_FEED action
return {... state, data: payload };
case constants.ANY_OTHER_ACTION_LOADING: // listen to LOAD_FEEDS
return {...state, loading: true }
...
default:
return state;
}
}
//ACTIONS
export function loadFeeds() {
return {
type: constants.LOAD_FEEDS
}
}
export function addFeed(payload) {
return {
type: constants.ADD_FEED,
payload
}
}
export function triggerAnyAction(payload) {
return {
type: constants.ANY_ACTION,
payload
}
}
These actions above may be dispatched from any component, be it Header or Feeds, only reducer(s) listening to that particular action will update the store.
Briefly, you only need to know which action to dispatch where and only reducer listing to that action will do whatever you instructed it to do

React Native Connect Redux Action & Reducer

I have been trying to connect my Redux Action and Reducer to my component. But it doesn't seem to work properly.
Currently, when I call my Action, it does get to that Action but it does not move onto my reducer. I think I am missing something here but having a hard time finding out what is the issue.
Could anyone please help me with this issue?
Thank you.
Here is my Action:
export const getItem = () => {
return (dispatch, getState) => {
debugger;
dispatch({
type: 'API_REQUEST',
options: {
method: 'GET',
endpoint: `18.222.137.195:3000/v1/item?offset=0`,
actionTypes: {
success: types.GET_ITEM_SUCCESS,
loading: types.GET_ITEM_LOADING,
error: types.GET_ITEM_SUCCESS
}
}
});
};
};
Here is my Reducer:
export const initialState = {
getItem: {}
};
const registerItemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ITEM_LOADING:
debugger;
return { ...state, loading: true, data: null };
case types.GET_ITEM_SUCCESS:
debugger;
return { ...state, loading: false, getItem: action.data};
case types.GET_ITEM_ERROR:
debugger;
return { ...state, loading: false, error: action.data};
default: {
return state;
}
}
}
export default registerItemReducer;
Here is my store:
/* global window */
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage'; // default:
localStorage if web, AsyncStorage if react-native
import thunk from 'redux-thunk';
import reducers from '../reducers';
// Redux Persist config
const config = {
key: 'root',
storage,
blacklist: ['status'],
};
const reducer = persistCombineReducers(config, reducers);
const middleware = [thunk];
const configureStore = () => {
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(applyMiddleware(...middleware)),
);
const persistor = persistStore(
store,
null,
() => { store.getState(); },
);
return { persistor, store };
};
export default configureStore;
Lastly here is my component that has "connect" part & componentDidMount:
componentDidMount() {
this.props.getItem();
}
const mapStateToProps = state => ({
registerItem: state.registerItem || {},
});
const mapDispatchToProps = {
getItem: getItem
};
export default connect(mapStateToProps, mapDispatchToProps)(RegisterItemComponent);
Is registerItem name of your reducer? Your reducer has two state getItem and loading. But in the below code you are calling state.registerItem. Looks like there is some mismatch between the actual state and the mapped state.
In the code below, try to print the state value, it will help you to navigate to the exact parameter you are looking for.
Add the below line in your existing code to debug:
const mapStateToProps = state => ({
console.log("State of reducer" + JSON.stringify(state));
registerItem: state.registerItem || {},
});

Redux - Combining multiple reducers result in state clearing

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;
}
}

Resources