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
Related
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;
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.
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 trying to add Redux to my test app but I'm having trouble fetching data from my API. It feels like I've gone through all of the steps but I can't access the props from the fetch in my component, so I'm messing up somewhere along the way. My code:
actions/index.js:
import 'whatwg-fetch';
import ReduxThunk from 'redux-thunk';
export const FETCH_RECIPES = 'FETCH_RECIPES';
const ROOT_URL = 'http://myapi/recipe/';
export function fetchRecipes(id) {
const url = ROOT_URL + "0";
// const request = fetch(url);
return (dispatch) => {
fetch(url)
.then((response) => response.json())
.then((request) => dispatch(fetchRecipesSuccess(request)))
};
}
export function fetchRecipesSuccess(request) {
return {
type: FETCH_RECIPES,
request
};
}
reducer_recipe.js:
import { FETCH_RECIPES } from "../actions/index";
export default function(state = [], action) {
switch(action.type) {
case FETCH_RECIPES:
return [action.payload.data, ...state];
}
return state;
}
reducers/index.js:
import { combineReducers } from 'redux';
import RecipesReducer from './reducer_recipes';
const rootReducer = combineReducers({
recipes: RecipesReducer
})
export default rootReducer;
aaaaand in my component I'm using this code:
function mapStateToProps({ recipes }) {
return { recipes };
}
connect(mapStateToProps, {fetchRecipes})(Recipe);
And in my index.js I'm creating my store with const createStoreWithMiddleware = createStore(reducers, applyMiddleware(ReduxPromise));
With my thinking I should be good to use this.props to access the data I've fetched from my API but I guess I'm dropping the data somewhere along the way. What am I missing?
Check your reducer well. You seem to be returning action.payload.data whereas in your fetchRecipesSuccess, it's named request. And you can console.log the action object to see what you've got
import { FETCH_RECIPES } from "../actions/index";
export default function(state = [], action) {
switch(action.type) {
case FETCH_RECIPES:
// Verify here that your request object has data
return [...state, action.request.data];
// Default state
default:
return state;
}
}
Hope this helps!