I have a React app built using Redux and Redux-Thunk. Everything works fine, until I try to combine reducers per the Redux docs.
Given an initial, functional reducer
export default function bigReducer(state = { events: [], flash: [] }, action) {
switch (action.type) {
case EVENTS_UPDATED:
return _.extend({}, state, { events: action.pathway_events })
case FLASH_MESSAGE_UPDATED:
return _.extend({}, state, { flash: action.flash })
default:
return state
}
}
When I try to create a composite reducer
function flashReducer(state = { flash: [] }, action) {
switch (action.type) {
case FLASH_MESSAGE_UPDATED:
return _.extend({}, state, { flash: action.flash })
default:
return state
}
}
function eventReducer(state = { events: [] }, action) {
switch (action.type) {
case EVENTS_UPDATED:
return _.extend({}, state, { events: action.pathway_events })
default:
return state
}
}
// either with simple reducer composition
export default function bigReducer(state = {}, action) {
return {
flashReducer: flashReducer(state.flash, action),
eventReducer: eventReducer(state.events, action)
}
}
// or with the combineReducers function
export default const reducer = combineReducers({
flashReducer,
eventReducer
})
the initial state and the reducers seem to get mixed up
// logging the state
var EventListContainer = connect((state) => {
console.log(state)
return { events: state.events })(React.createClass({ ...
// returns the incorrect state
# => Object {flashReducer: Array[0], eventReducer: Array[17]}
How can I combine reducers using React and Redux?
My understanding from the docs is that a named reducer is delegated to handle only that part of the state with the top-level key corresponding to the reducer name. So
const reducer = combineReducers({
flashReducer,
eventReducer
})
implies that you have state like
const state = {
flashReducer: {...},
eventReducer: {...}
}
So you need to a) name your reducers the same as the top-level keys they're supposed to manage, and b) have their default state only represent that subset of the full state object:
function flash(state = [], action) {
switch (action.type) {
case FLASH_MESSAGE_UPDATED:
return action.flash.slice()
default:
return state
}
}
function events(state = [], action) {
switch (action.type) {
case EVENTS_UPDATED:
return action.pathway_events.slice()
default:
return state
}
}
const reducer = combineReducers({
flash,
events
})
Related
I have two dashboards which contains similar data and properties that I want to implement in redux. See below.
Dashboard 1 : {filters, widgets, custom}
Dashboard 2: {filters, widgets, custom}
I want to create my redux state like so:
{
dashboards: {
dashboard1:{
filter:{ // filter related stuff},
widgets:{ // widget related state},
custom:{ // custom state}
},
dashboard2: {
filter:{ // filter related state},
widgets:{ // widget related state},
custom:{ // custom state}
}
},
auth: {// auth related stuff},
...// other state keys
}
In order to achieve this I am trying to use combine reducers like this for dashboards
// xyz.reducer.js
combineReducers({
filters: filterReducers,
widgets: widgetReducers,
custom: CustomReducers
})
now I have created a dashboard reducer, which is called every time user navigates to a certain dashboard. I want to assign this state based on dashboardID. something like -
const dashboardReducer = (state = defaultState, action) => {
switch (action.type) {
case CREATE_DASHBOARD: {
const { payload } = action;
return {
...state,
[payload.dasboardId]: // all the combined reducer state here
};
}
default:
return state;
}
};
Can someone suggest me how can I achieve this? I am also open for suggestion if someone have a better
way of maintaing this kind of state structure.
Sure, you can do this as long as you have a way to identify all actions that are relevant to the xyz reducer and have away to identify the dashboardId.
For your example, assuming the nested reducer from xyz.reducer.js is called xyz:
import xyz from './xyz.reducer.js'; // or whatever
const dashboardReducer = (state = defaultState, action) => {
switch (action.type) {
case CREATE_DASHBOARD: {
const { payload } = action;
return {
...state,
[payload.dashboardId]: xyz(state[payload.dashboardId], action),
};
}
default:
return state;
}
};
Let's say you know all of the action types that the dashboard might need to respond to you and they all have dashboardId as a key in the payload you can simply pass all of those along.
const dashboardReducer = (state = defaultState, action) => {
switch (action.type) {
case CREATE_DASHBOARD:
case UPDATE_DASHBOARD_FILTER:
case ADD_DASHBOARD_WIDGET:
//... etc
{
const { payload } = action;
return {
...state,
[payload.dashboardId]: xyz(state[payload.dashboardId], action),
};
}
default:
return state;
}
};
This is a little brittle so you may want to do something like assume that any payload with a dashboard id should be passed on:
const dashboardReducer = (state = defaultState, action) => {
const {payload = {}} = action;
if (typeof payload.dashboardId !== 'undefined') {
return {
...state,
[payload.dashboardId]: xyz(state[payload.dashboardId], action),
};
}
return state;
};
Now you don't need to keep up the list of action types, you just need to ensure that all relevant action creators include a dashboardId in their payload.
I am trying to use stackoverflow api to make my first react redux project. I need to maintain a state like the following:
{
selectedTag: reactjs,
selectedSortOrder: activity,
items:[]
}
My reducer is given below:
const initialState = {
selectedTag: 'C#',
selectedSortOrder: 'activity', items: []
}
function SelectTag(state = initialState, action) {
switch (action.type) {
case SELECTTAG:
// console.log(state);
return Object.assign({}, state, { selectedTag: action.selectedTag });
default:
return state;
}
}
function SelectSortOrder(state = initialState, action) {
switch (action.type) {
case SELECTSORTORDER:
//console.log(state);
return Object.assign({}, state, { selectedSortOrder: action.selectedSortOrder });
default:
return state;
}
}
function ReceivePosts(state = { items: [] }, action) {
switch
(action.type) {
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
console.log(state);
return Object.assign({}, state, { items: action.items })
default:
return state
}
}
const rootReducer = combineReducers({ ReceivePosts, SelectTag, SelectSortOrder })
And mapStateToProps is:
const mapStateToProps = (state) => {
const selectedTag = state.SelectTag.selectedTag;
const items = (state.ReceivePosts.items);
const tags = (state.ReceiveTags.tags);
const selectedSortOrder = state.SelectSortOrder.selectedSortOrder;
return {selectedTag, items, tags, selectedSortOrder};
}
I have 2 problems here:
a. State does not remember all the data. For eg. suppose I select the tag first and then get items, my state has only items. SelectedTag is not set in the state.
b. I am not sure why mapStateToProps needs the reducer name. Eg: const selectedTag = state.SelectTag.selectedTag;
Actually it should be state.selectedTag. But my code expects the reducer name "SelectTag" to fetch the state value.
What am I doing wrong?
You haven't configured your reducers correctly. The initialState is assigned to all of your reducers which isn't required
const initialState={
selectedTag:'C#',
selectedSortOrder:'activity',
items:[]
}
function SelectTag(state = initialState.selectedTag, action){
switch(action.type){
case SELECTTAG:
return action.selectedTag
default:
return state;
}
}
function SelectSortOrder(state = initialState.selectedSortOrder, action){
switch(action.type){
case SELECTSORTORDER:
return action.selectedSortOrder
default:
return state;
}
}
function ReceivePosts(state = {items:[]}, action){
switch(action.type){
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
console.log(state);
return Object.assign({}, state, {items:action.items})
default:
return state
}
}
const rootReducer = combineReducers({ReceivePosts, SelectTag, SelectSortOrder})
And in mapStateToProps you would use it like
const mapStateToProps = (state) => {
const selectedTag = state.SelectTag;
const items = (state.ReceivePosts.items);
const tags = (state.ReceiveTags.tags);
const selectedSortOrder = state.SelectSortOrder;
return {selectedTag, items, tags, selectedSortOrder};
}
1. Try this code change
const initialState = {
selectedTag: 'C#',
selectedSortOrder: 'activity',
items: []
}
function SelectTag(state = initialState.selectedTag, action) {
switch (action.type) {
case SELECT TAG:
return {
...state,
selectedTag: action.selectedTag
}
default:
return state;
}
}
function SelectSortOrder(state = initialState.selectedSortOrder, action) {
switch (action.type) {
case SELECTSORTORDER:
return {
...state,
selectedSortOrder: action.selectedSortOrder
}
default:
return state;
}
}
function ReceivePosts(state = { items: [] }, action) {
switch (action.type) {
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
return {
...state,
items: action.items
}
default:
return state
}
}
const rootReducer = combineReducers({ ReceivePosts, SelectTag, SelectSortOrder });
2. I am not sure why mapStateToProps needs the reducer name. Eg: const selectedTag = state.SelectTag.selectedTag;
Its because when you use combinereducers, you are combining multiple slices of data, then you need to specify the slice from which you want to fetch the data.
const rootReducer = combineReducers({
receivePosts = ReceivePosts,
selectTag = SelectTag,
selectSortOrder = SelectSortOrder
});
Issue: You have not configured your initialstate properly, you are using the same initialstate in SelectTag and also in SelectSortOrder, if the initial state is same then why do you need two reducers?
I have made component Notification and I want its action and reducer to be available to all the app.
But I am not getting why my action is saying not defined.
Here is the code
import { SHOW_NOTIFICATION, SHOW_NOTIFICATION_FULFILLED } from 'constants/actionTypes'
// notification
const initialState = {
notification: {}
}
export function fetchNotification (data) {
return {
type: 'SHOW_NOTIFICATION',
data: data
}
}
export default function notificationReducer (state = initialState, action) {
switch (action.type) {
case SHOW_NOTIFICATION_FULFILLED: {
return {
...state,
notification: action.data
}
}
}
return state
}
SHOW_FULFILLED is showing defined but never used so whats the mistake
You call action with SHOW_NOTIFICATION but reducer handle only SHOW_NOTIFICATION_FULFILLED. Use same action type in reducer as in action.
// notification
const initialState = {
notification: {}
}
export function fetchNotification (data) {
return {
type: 'SHOW_NOTIFICATION', // <--
data: data
}
}
export default function notificationReducer (state = initialState, action) {
switch (action.type) {
case SHOW_NOTIFICATION: { // <--
return {
...state,
notification: action.data
}
}
}
return state
}
I have a reducer that looks like this:
const chart = combineReducers({
data,
fetchProgress,
fetchError,
updateProgress,
updateError,
});
I now would like to not only a chart but multiple charts.
const charts = (state = {}, action = {}) => {
if (action.type == FETCH_CHART || action.type == ...) {
let newChart = chart(state[action.id], action);
return Object.assign({}, state, {[action.id]: newChart});
}
return state;
}
Is there something conceptually wrong to do this?
If no, is there a better way to achieve the same result?
There is nothing wrong with the concept. In fact, I'd say this is my preferred approach when needing to store similar data in the redux store
To improve it, you could wrap it in a higher-order reducer to handle the id part of it. Something like:
const handleIds = (reducer) => (state = {}, action) => {
if (action.id) {
let idState = state[action.id]
let newState = reducer(idState, action)
if (newState !== idState) {
return { ...state, [action.id]: newState }
}
}
return state
}
This will pass on any action with an id and merge the resulting state into it's state with that id as it's key, if the state has changed.
Then your reducer becomes:
const singleChart = (state = {}, action = {}) => {
if (action.type == FETCH_CHART || action.type == ...) {
let newChart = chart(state, action);
return newChart;
}
return state;
}
const charts = handleIds(singleChart)
Then combine it into your store:
const chart = combineReducers({
data,
fetchProgress,
fetchError,
updateProgress,
updateError,
charts
});
Personally I would breakdown the logic to further sub reducers in order to have a better separation of concerns. In case you will add multiple charts and in case you will need to add more logic/settings/data to your actions, you will end up to modify too much your single reducer.
I follow with a small example where you could have 3 charts.
// bubbleChartReducer.js
export function bubble (state = {}, action) {
switch (action.type) {
case 'FETCH_BUBBLE_CHART':
return {
[action.id]: new chart(action.id, action)
}
default:
return state
}
}
// pieChartReducer.js
export function pie (state = {}, action) {
switch (action.type) {
case 'FETCH_PIE_CHART':
return {
[action.id]: new chart(action.id, action)
}
default:
return state
}
}
// linearChartReducer.js
export function pie (state = {}, action) {
switch (action.type) {
case 'FETCH_LINEAR_CHART':
return {
[action.id]: new chart(action.id, action)
}
default:
return state
}
}
// chartsReducer.js
import { bubble } from 'bubbleChartReducer'
import { pie } from 'pieChartReducer'
import { linear } from 'linearChartReducer'
import { combineReducers } from 'redux'
export combineReducers({
bubble,
pie,
linear
})
Is it possible to create multiple redux-forms reducers?
for example I would like the application state to look like this:
activities: {
login: {
form: {
// ... all login form data
}
// ... more login data
},
customer: {
form: {
// ... all customer form data
}
// ... more customer data
}
}
so basically, is it possible to connect a form component to a specific reducer or always works with a single reducer and only the form names are dynamic?
I think it's possible but in such case you have to tell given redux-form decorator where corresponding reducer was mounted. There is a getFormState config property in reduxForm which expects function that according to docs:
..takes the entire Redux state and returns the state
slice which corresponds to where the redux-form reducer was mounted.
This functionality is rarely needed, and defaults to assuming that the
reducer is mounted under the form key
So since you can define reducer for given form you can use multiple reducers.
For more details check redux-form reducer and reduxForm decorator,
USE combineReducers or this pattern :
const recipeReducers = (recipes , action) =>{ //recipes --- state.recipes
switch (action.type) {
case action_type.ADD_RECIPE:{
return recipes.concat({name: action.name})
};
default:
return recipes
}
}
const ingredientsReducer = (ingredients, action) =>{
switch (action.type) {
case action_type.ADD_INGREDIENT:{
const newIngredient = {
name: action.name,
recipe: action.recipe,
quantity: action.quantity
};
return ingredients.concat(newIngredient)
};
default:
return ingredients
}
}
const reducer = (state={}, action) => {
return Object.assign({}, state, {
recipes: recipeReducers(state.recipes, action),
ingredients: ingredientsReducer(state.ingredients, action)
});
};
const initialState = {
recipes: [
{
name: 'Omelette'
}
],
ingredients: [
{
recipe: 'Omelette',
name: 'Egg',
quantity: 2
}
]
};
function configureStore(initialState = {}) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk)
)
return store;
};
window.store = configureStore(initialState);
store.subscribe(() => console.log(store.getState()))