RRP
RRP

Reputation: 2853

Sharing state between two reducers

I have two reducers, the first one fetches data and the second one is used to filter the data based on changes to the original data. I have read that using combineReducers is not an option.

My postReducer

import {FETCH_POST} from '../actions/types';

const initialState = {
    items: [],
    item: {}
};

export default function(state = initialState, action){
    switch (action.type){
        case FETCH_POST:
            return Object.assign({}, state, {items: action.payload});
        default:
            return state;
    }

}

My pinReducer

import {PIN_POST} from '../actions/types';

const initialState = {
    items: [],
    item: {}
};

export default function(state = initialState, action){
    switch (action.type){
        case PIN_POST:
            console.log(state.items);
            const item = state.items.map(value => value.data.id === action.id ?
                {data: Object.assign({}, value.data, {pinned: action.val})} : value
            );
            //console.log(item);
            return Object.assign({}, state, {items: item });
        default:
            return state;
    }

}

Main

import {combineReducers} from 'redux';
import postReducer from './postReducer';
import pinReducer from './pinReducer';

export default combineReducers({
    post: postReducer,
    pin: pinReducer
});

How can I share the state between the two reducers, as state.items in the pinReducer is empty

Upvotes: 4

Views: 5978

Answers (2)

amankkg
amankkg

Reputation: 5081

Reducer does not access to store's state, nor other reducers' previous state. Only to its own previous state and dispatched action. You can think of it as a single brick in the wall of redux' state.

In your case, we have to tackle the logic and decide what is an actual state here.

Requirements:

  1. fetch a list of posts: state is an array of posts;
  2. pin one of these posts: state is an id of currently pinned post;
  3. get currently pinned post: find a post in an array by pinned post id.

Solution:

1 - initial posts reducer:

const initialState = { items: [] }

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_POSTS:
      return { items: action.payload }
    default:
      return state
  }
}

2 - add info about pinned post:

const initialState = { items: [], pinnedId: null }

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_POSTS:
      return { ...state, items: action.payload.posts }
    case PIN_POST:
      return { ...state, pinnedId: action.payload.id }
    default:
      return state
  }
}

3 - get a currently pinned post:

// assuming that store is configured...
export default combineReducers({ posts: postsReducer })
// ...

// somewhere in `connect`
const mapStateToProps = (state, ownProps) => {
  return {
    pinnedPost: state.posts.find(post => post.id === state.posts.pinnedId),
  }
}

So now we store a minimum information about a pinned post (only its id) and it is enough to get an actual pinned post.

Also, as mentioned in another answer by @shubham-khatri, you can use optimized selectors. With current shape of the state storing pinned id separately, it is even more efficient since your selector will depend on two inputs: posts array (is re-created on fetch, so shallow comparable) and pinned id (is a primitive number or string, also easy to compare).

I'd encourage you to store as minimum information in store as possible. Also, store it in simple and flat shape. Move everything that's can be calculated to selectors.

Upvotes: 1

Shubham Khatri
Shubham Khatri

Reputation: 282160

You should not have a reducers that has state derived on the basis of other reducers. What you need in your case is a selector(more efficiently a memoizedSelector), for which you can use reselect library

My postReducer

import {FETCH_POST} from '../actions/types';

const initialState = {
    items: [],
    item: {}
};

export default function(state = initialState, action){
    switch (action.type){
        case FETCH_POST:
            return Object.assign({}, state, {items: action.payload});
        default:
            return state;
    }

}

Main

import {combineReducers} from 'redux';
import postReducer from './postReducer';

export default combineReducers({
    post: postReducer,
});

and then where you want to use pinItem, you can do that in mapStateToProps

getPinItem = (state, props) => {
    const item = state.items.map(value => value.data.id === action.id ?
                {data: Object.assign({}, value.data, {pinned: action.val})} : value
    );
    return item;
}

const mapStateToProps = (state, props) => {
    const pinItem = getPinItem(state, props);
    return {
        pinItem
    }
}

Upvotes: 1

Related Questions