How to manage nested reducers in Redux? - reactjs

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.

Related

Can we change array stored in Redux?

I have a product filter array which i stored in redux, initially its an empty array and i want to dispatching an object in it one by one .
The issue is i want to check if the object_id is already is in the array then i don't want to dispatch it and as well as delete that particular object in redux state.
Likewise,
if user added blue color filter so its accept but if user again added blue color filter so its shouldn't dispatch this object and also remove this object in redux.
reducer:
filterd :[]
const filterreducer = (state = initialState, action) => {
switch (action.type) {
case ADD_FILTER:
return {
...state,
filterd :[...state.filterd,action.payload]
};
default:
return state;
}
}
export default filterreducer ;
initialState ={
filterd :[]
}
const filterreducer = (state = initialState, action) => { switch (action.type) {
case ADD_FILTER:
return {
...state,
filterd :action.paylod
};
default:
return state;
}
}
export default filterreducer ;
you have to store response data in payload while dispatching that action
such as:
dispatch({
type:ADD_FILTER,
payload: response.data
)

react redux don't show getStore

I'm new to React Redux.
It's fantasic so that
I want to see about tasks state of Store.
but don't see it.
result
tasks: [undefined]
Ask
so how can I?
here code:
import { createStore } from 'redux'
const initalState = {
tasks: []
}
function tasksReducer(state = initalState, action) {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: state.tasks.concat([action.task])
};
default:
return state;
}
}
const addTask = (task) => ({
type: 'ADD_TASK',
payload: {
task
}
})
const store = createStore(tasksReducer)
store.dispatch(addTask('like it'))
console.log(store.getState()) <-- here
The problem is your action. It's adding a task with a key of payload, but you're trying to concat it with action.task (Which doesn't exist).
The object that would be sent into your reducer would look like this:
{
type: 'ADD_TASK',
payload: {
task: 'like it'
}
}
You can see clearly here, action.task doesn't exist, but action.payload.task does. Either change the object around, or modify it so you can access it at action.task:
const addTask = (task) => ({
type: 'ADD_TASK',
task
})
Or, modify your reducer:
function tasksReducer(state = initalState, action) {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: state.tasks.concat([action.payload.task])
};
default:
return state;
}
}
In the future: a bit of debugging would have gone a long way here (And avoided this question altogether). A simple console.log(action) at the top of your reducer, would log the above object, and you could infer based on why it's trying to add undefined why it wouldn't work.

Reducer that returns a collection of reducer results

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

Redux: Distinguish objects in reducers

I'm quite new to Redux and from what I understand, a reducer should be created for each type of object. E.g. for user interaction a user reducer should be created. My question is: How do you handle cases where you require the object for different purposes?
Scenario: Imagine having a user reducer which returns the current user. This user would be required in the entire application and needed for general controls on every page.
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
In this case there would be a conflict if the user reducer would be used. What would be the correct way to handle this in Redux? In case a different reducer would have to be created, what would be the naming convention for the new reducer?
First, you've mentioned:
a user reducer which loads the current user
I don't know if I got you correctly, but if this means you want to fetch (from an API, for example) the current user inside the reducer, this is a wrong approach.
Reducers are intended to be pure functions. You can call them with the same arguments multiple times and they will always return the same expected state.
Side effects like that should be handled by action creators, for example:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
// it's using redux-thunk (withExtraArgument: api) module to make an async action creator
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
Inside your reducer you can simple get the data and set a new state (note that if you send the action with the same data multiple times, the state will always be the same).
reducers/user.js
import { FETCH_ME, FETCH_ME_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
export const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
Now, for your scenario:
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
You will just write another action creator for that:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
export const FETCH_USER = 'FETCH_USER'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
export const fetchUser = (id) => (dispatch, getState, api) => {
dispatch({ type: FETCH_USER })
return api.get(`/users/${id}`).then(({ data }) => {
dispatch({ type: FETCH_USER_SUCCESS, data })
return data
})
}
Then you adapt your reducer to manage more sets:
reducers/user.js
import { combineReducers } from 'redux'
import { FETCH_ME, FETCH_ME_SUCCESS, FETCH_USER, FETCH_USER_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
const meReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
case FETCH_ME_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const activeReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_USER_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_USER_SUCCESS:
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
export default combineReducers({
activeUser: activeReducer,
me: meReducer
})
Your final user state should be something like:
{
me: {
item: null,
loading: false
},
active: {
item: null,
loading: false
}
}

React-Redux: Combining reducers fails

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

Resources