I am using redux combineReducers() to combine 2 reducers. While they are both recognised in dev tools, one of them is simply an empty object, even though I passed an object with different properties into the reducer.
The combineReducers() file:
import { combineReducers } from 'redux'
import { default as JobReducer } from './Jobs/reducer'
import { default as AuthReducer } from './Auth/reducer'
const RootReducer = combineReducers({
job: JobReducer,
auth: AuthReducer,
})
export default RootReducer
The first reducer:
import { SINGLE_JOB_POST_SUCCESS } from './constants'
const initial = {
jobs: [],
job: {}
}
export default (state = initial, action) => {
const { type, payload } = action
switch (type) {
case SINGLE_JOB_POST_SUCCESS:
return {
...state,
jobs: [
...state.jobs,
...payload
]
}
default:
return {}
}
}
The second reducer:
import { RESET_AUTH_RESPONSE } from './constants'
const initial = {
authResponse: null,
user: {}
}
export default (state = initial, action) => {
const { type, payload } = action
switch (type) {
case RESET_AUTH_RESPONSE:
return {
...state,
authResponse: null
}
default:
return state
}
}
When I look at the state in redux dev tools, "auth" has the relevant properties, but "job" is simply an empty object. I have called the reducer files different names so as to remove the need for aliases but it had no effect. Any help is appreciated.
Because your authReducer has:
default:
return {}
So, any time it sees an action it doesn't recognize, it'll return an empty object. You need it to be return state instead.
Also, note that you should switch to using our official Redux Toolkit package, which will simplify your Redux logic considerably.
Related
I'm fairly new to Redux and I'm trying to understand why is the combineReducers function calling the reducers twice.
My reducer/index.js looks like this
import {combineReducers} from "redux"
const AReducer = (state = "A", action) =>{
console.log("In A reducer")
switch (action.type){
case "Alpha":
return state + action.payload
default:
return state
}
}
const BReducer = (state = "B", action) =>{
console.log("In B reducer")
switch(action.type)
{
case "Beta":
return state + action.payload
default:
return state
}
}
const allReducers = combineReducers({
A : AReducer,
B : BReducer
})
export default allReducers
and my store/index.js looks like this
import {createStore} from "redux";
import allReducers from "../Reducer"
const store = createStore(allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
And the console log is displaying this
index.js:4 In A reducer
index.js:4 In A reducer
index.js:15 In B reducer
index.js:15 In B reducer
index.js:4 In A reducer
index.js:15 In B reducer
I only want to understand why it's behaving like this. I want a better grasp around what's going on in the background
First of all, let's print the action.
import { combineReducers, createStore } from 'redux';
const AReducer = (state = 'A', action) => {
console.log('In A reducer, action: ', action);
switch (action.type) {
case 'Alpha':
return state + action.payload;
default:
return state;
}
};
const BReducer = (state = 'B', action) => {
console.log('In B reducer, action: ', action);
switch (action.type) {
case 'Beta':
return state + action.payload;
default:
return state;
}
};
const allReducers = combineReducers({
A: AReducer,
B: BReducer,
});
const store = createStore(allReducers);
The logs:
In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In A reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' }
In B reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In B reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONu.8.f.5.c.h' }
In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In B reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
Explanation
I use AReducer's logs to explain, Breducer is also the same.
combineReducers function calls assertReducerShape() function internally.
assertReducerShape() function will invoke each reducer passed in the combineReducers function with a init action to check if the reducer has a valid returned value. This is how In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' } log come.
And, it also invokes each reducer with unknown actions to check if the reducer return the current state for any unknown actions unless it is undefined. This is how In A reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' } log come.
When calling the createStore function, it will dispatch init action. So that every reducer returns their initial state. This effectively populates the initial state tree. This is how In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' } log come. This process is mentioned in the documentation, See tips.
Also take a look at the INIT and PROBE_UNKNOWN_ACTION action types in utils/actionTypes file.
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `##redux/INIT${/* #__PURE__ */ randomString()}`,
REPLACE: `##redux/REPLACE${/* #__PURE__ */ randomString()}`,
PROBE_UNKNOWN_ACTION: () => `##redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
These action types are private, and used by redux internally, you don't need to handle them. You will see the dispatched INIT action in redux dev tools, don't be surprised.
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;
In my project, I am trying to combine two reducers but whenever I try to combine them using combineReducers({}) my props become undefined and my state ( from store.getState() ) turns out to be two objects name after my reducers. See my reducer setup
root/reducers.js
import { combineReducers } from 'redux'
import dashboardReducer from '../views/DashboardPage/redux/reducers'
import formReducer from '../views/FormPage/redux/reducers'
export default combineReducers({
dashboardReducer,
formReducer
})
configureStore.js
import { createStore, compose } from "redux";
import rootReducer from "./reducers";
const storeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer);
export default store;
dashboard/reducer.js
import {ADD_FIELD} from "./types"
const initialState = {
fields: [{title: "bla", text: "jorge", id: 1}],
};
const dashboardReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_FIELD:
return {
...state,
fields: state.fields.concat(action.payload)
};
default:
return state;
}
};
export default dashboardReducer;
forms/reducer.js
const formReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state
}
};
export default formReducer;
calling console.log on "store.getState()" and "this.props" (after mapping state to props) returns the following, respectively:
console.log's
if it matters, I am using react-router
That is correct behaviour and it sounds like you need to modify your mapStateToProps to handle it correctly, so you might want to add your mapping function to your question.
export default combineReducers({
dashboardReducer,
formReducer
})
Using combineReducers means that you will manage different slices of your state with the different reducers and these slices will be named after the keys in the object you provide. You probably want to change that to be:
import dashboard from '../views/DashboardPage/redux/reducers'
import form from '../views/FormPage/redux/reducers'
export default combineReducers({
dashboard,
form
})
this will result in your state having the shape:
{
dashboard: { dash: "things" },
form: {}
}
Your dashboard reducer will be called with state set to
{ dash: "things" }
and your mapStateToProps will need to read the state accordingly
return {
fields: state.dashboard.fields
};
I have a left Sidebar that I want to share State of throughout my application so if user clicks on slider's button, user can make it hidden or show. Initially I plan to have it shown. I was following a Redux course and in the coure they did an example of fetching posts from API which is entirely different thant what I need so I am puzzled here...
So far, I created a folder called actions with 2 files,
sliderActions.js
import { Slider } from "./types"
export function sliderUpdate() {
return function(dispatch) {
dispatch({
status: "hidden"
})
}
}
and types.js
export const Slider = "Slider";
reducers folder has two files
index.js
import { combineReducers } from "redux"
import postReducer from "./postReducer"
export default combineReducers({
posts: postReducer
})
and postReducer.js
import { Slider } from "./../actions/types"
const initialState = {
Slider: "hide"
}
export default function(state = initialState, action) {
switch(action.type) {
default:
return state;
}
}
store.js file
import { createStore, applyMiddleware } from "redux"
import { thunk } from "redux-thunk"
import rootReducer from "./reducers"
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
applyMiddleware(...middleware)
)
export default store
and lastly
I imported below two to the App.js
import { Provider } from "react-redux"
import { store } from "./store"
and wrapped my whole code inside return statement of App with a <Provider store={store}> and </Provider>
I am entirely new to the redux and don't know how to get this working, any help would be appreciated!
Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants.
So your actions file should be something like this
import { SLIDER } from "../constant/slide";
export function sliderUpdate() {
return function(dispatch) {
dispatch({
type: SLIDER,
status: "hidden"
});
};
}
Where the constant SLIDER
export const SLIDER = "SLIDER";
And the reducer should look like this
import { SLIDER } from "../constant/slide";
const initialState = {
Slider: "hide"
};
export default function(state = initialState, action) {
switch (action.type) {
case SLIDER:
return Object.assign({}, state, action.data);
default:
return state;
}
}
We don't mutate the state.So We create a copy with Object.assign() where it will contain the new data.
Documentation says: Actions may not have an undefined "type" property.
Change your action in your sliderUpdate function and add 'type' key.
For example:
dispatch({
type: "SLIDER_BUTTON_CLICKED",
status: "hidden",
});
and now you want to change your postReducer to:
const initialState = {
slider: "opened"
}
export default function(state = initialState, action) {
switch(action.type) {
case "SLIDER_BUTTON_CLICKED": {
return {...state, slider: action.status}
}
default:
return state;
}
}
I'm having a really strange issue in react in which my reducers seem to be modifying parts of the state that they shouldn't, I'm sure this is due to some oversight in my implementation, but I can't track it down.
I currently have one store, two actions/action creators, two reducers, and one root reducer. Here is my code, (left out some unimportant details):
//actionTypes.ts
export const SUBMIT_SEARCH = "SUBMIT_SEARCH";
export const CHANGE_SEARCH_PAGE = "CHANGE_SEARCH_PAGE";
-
//actionCreators.ts
import { SUBMIT_SEARCH, CHANGE_SEARCH_PAGE } from "./actionTypes"
export const submitSearch = (submitSearch) => ({
type: SUBMIT_SEARCH,
submitSearch
});
export const changeSearchPage = (changeSearchPage) => ({
type: CHANGE_SEARCH_PAGE,
changeSearchPage
});
-
//submitSearchReducer.ts
import { SUBMIT_SEARCH } from "../actions/actionTypes";
const submitSearch = (state = {}, action) => {
switch (action.type) {
case SUBMIT_SEARCH:
return {
...state,
submitSearch: action.submitSearch
}
default:
return state;
};
};
export default submitSearch;
-
//changeSearchPageReducer.ts
import { CHANGE_SEARCH_PAGE } from "../actions/actionTypes";
const changeSearchPage = (state = {}, action) => {
switch (action.type) {
case CHANGE_SEARCH_PAGE:
return {
...state,
changeSearchPage: action.changeSearchPage
}
default:
return state;
};
};
export default changeSearchPage;
-
//rootReducer.ts
import { combineReducers } from 'redux';
import submitSearch from "../reducers/submitSearchReducer"
import changeSearchPage from "../reducers/submitSearchReducer";
const rootReducer = combineReducers({
submitSearch: submitSearch,
changeSearchPage: changeSearchPage
});
export default rootReducer;
-
I create the store like this:
const logger = createLogger();
const store: Store<any> = createStore(rootReducer, {/*Initial state empty*/}, applyMiddleware(logger));
-
The general flow is like this:
User enters a search string on the page which triggers a store.dispatch(submitSearch(string)) call
User enters a number on the page which triggers a store.dispatch(changeSearchPage(newPage))
Here's the output from my logger:
[![Search call][1]][1]
[![change page call][2]][2]
You can see here that the state is clearly getting mixed up, and the wrong data is going to the changeSearchPage key.
What is causing this mix up?
In the rootReducer you're importing the same reducer twice