Reputation: 15914
Let's say I want to have the following state structure:
{
deep: { ... },
foo: 0,
bar: 0,
str: ''
}
the deep
may be complex, so I want to extract a dedicate reducer for it
const deepReducer = (state = {}, action) => { ... }
and for the rest part (foo, bar and str), just use another reducer for them because they are very simple
const initialState = { foo: 0, bar: 0, str: '' } // May also need to add deep here
const defaultReducer = (state = initialState, action) => { ... }
but this structure doesn't seem to work, if use combineReducers will group other reducers, then the structure will become:
{
deep: { ... },
others: {
foo: 0,
bar: 0,
str: '',
}
}
But I don't want to group them because their relationships are not that strong.
Is it possible to achieve this in 2 reducers? Or the only solution is to create a dedicate reducer for each state (foo, bar and str)?
Upvotes: 0
Views: 106
Reputation: 7180
I feel like you should tackle the issue of state management first. Then it will be easier to split your reducers appropriately.
TLDR Keep your reducers as simple as possible and use a combination of state normalisation and selectors in order to ensure that you don't unnecessarily complicate your reducers.
State Management
As the docs state
The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data.
First and foremost, it's important to understand that your entire application really only has one single reducer function
Every leaf (slice of data) in your state tree should have its own dedicated reducer. This is because each leaf represents a given domain of grouped data. Think of this state as a database. Each leaf would be representative of a table of grouped data.
State Normalisation
You say that you have a deeply nested object in your state.
{
deep: { ... },
foo: 0,
bar: 0,
str: ''
}
Instead of worrying about how to structure your reducers around this state shape I would instead try and simplify your state tree in the first instance.
If you normalize your state this will reduce the level of nesting in your state objects. Therefore the reducers won't have to deal with deep levels of nesting and as a result will be simpler.
Remember that in coding simplicity is beauty.
Selectors
Your state contain the minimal representation of data in your application.
If despite your best efforts you have a deeply nested (complex) object in your state then you employ your selectors. If you don't use selectors then your reducers will need to know how to extract information from state. This will add uneccessary logic to the reducers and bloat them undermining our apps modularity.
By using selectors we can compute derived data from our state in a modular way as selectors can be reused and composed.
How do we use selectors?
Using the analogy of our state as a front end database. Selectors represent our queries that fetch data.
Selectors Example
This example from the docs uses the popular reselect library to create a selector. In this example the derived data which is being computed from our state is the todos which are actually visible. The filtered list is derived from our list of all todo items and the current visibility filter which could be set to all, none or enabled.
const getKeyword = (state) => state.keyword
const getVisibleTodosFilteredByKeyword = createSelector(
[ getVisibleTodos, getKeyword ],
(visibleTodos, keyword) => visibleTodos.filter(
todo => todo.text.indexOf(keyword) > -1
)
)
If we didn't perform this todo filtering operation then we would have to store the filtered todos in state and update the stae each time our visibility filter changed.
Upvotes: 1