How do I define a reducer at the top level? - ngrx

I have two reducer files that handle the two areas of state in my module...
export interface MyState {
people: PeopleState;
pets: PetState;
}
export const reducers: ActionReducerMap<MyState> = {
people: peopleReducer,
pets: petsReducer
};
This works fine. However, I have some actions that need to update both areas of the state. I'd like to handle this by having a reducer file that deals with things at the MyState level and can update people and pets. I'd like to keep both the existing reducers that handle this at the lower levels.
I can't see how to register the top level reducer. The way the code is now, any reducer added to the ActionReducerMap must be added as a property inside MyState rather than handling MyState as a whole.
Anyone got any ideas?
Thanks
Nick

I guess that the only solution here is to use metaReducer, check this article: https://netbasal.com/implementing-a-meta-reducer-in-ngrx-store-4379d7e1020a
Description: metaReducer is a kind of reducer that stays above other reducers. It should have it's own actions, can have it's own effects. You can use it in such way:
export function metaReducer( reducer ) {
return function reduce( state: ParentState, action: MetaReducerAction ) {
switch(action.type)
case PARENT_ACTION:
return {
...state,
people: peopleReducer(state.people, new DoSthWithPeople()),
pets: petsReducer(state.pets, new DoSthWithPets())
}
default:
return reducer(state, action);
}
}
Where:
interface ParentState {
pets: PetsState,
people: PeopleState
}
type MetaReducerAction = ParentAction <-- this has type PARENT_ACTION
So the workflow is straight forward. In a place that you want a action to update both people and pets states, you need to dispatch PARENT_ACTION, then the actions DoSthWith... are going to be triggered on both slices of state. If you dispatch different action (the action of type that is not handled by the metaReducer, so sth different to PARENT_ACTION) then it will allow other reducers to handle the action (check whats in default section).
The last part is configuration, it should look like this:
StoreModule.forFeature(compose(metaReducer, combineReducers)(reducers))
Where reducers is just:
const reducers = {
pets: petsReducer,
people: peopleReducer
}
Edit: formatting

Related

How to work with Redux reducers and server API

What is a better way to create reducers with handleActions in redux-actions:
1. Create reducers for each CRUD operations (like add data, delete data) and combine it. How set initialState in this case?
2. Set actions in one reducer (fetchDeleteDataRequest, fetchDeleteDataSuccess, fetchAddDataRequest, fetchAddDataSuccess by example)?
You can have sperate reducers and or common reducers to add or delete data that is up to you. If you are considering separate actions for add and delete you will need to keep the data consistent. But having a common function to deal with the CRUD operations is ideal since this will reduce the amount of code that you have to use but you will need a way to distinguish them as well (maybe bypassing some variable ADD or DELETE). let's consider and list of items that you will be adding or deleting. This list can be an empty array in the beginning (initialState) and pass it as props to your component.
Actions
export const addDeleteItem = data => dispatch => {
// you can perform REST calls here
dispatch({
type: ADD_REMOVE_DATA,
payload: data
});
};
Reducers
let initialState = {
items:[]
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_REMOVE_DATA:
if(action.payload.event === 'ADD'){
return {...state,items:[...state.items,action.payload.item]}
}else if(action.payload.event === 'DELETE'){
return {...state,items:state.items.filter(item=>item.id !== action.payload.item.id)}
}else if (action.payload.event === 'UPDATE'){
//handle your update code
}
}
}
This is just an example you can follow something like this to avoid code duplication.

Redux store having multiple 'version' of states under reducer names

I was trying to have separated reducer files for each container file I have, resulting in different reducer exports for each container and I combine it with combineReducers.
I want my store to have state that structured like so:
state = {
currentFilter: 'all',
todos: [],
displayed: [],
}
But instead get a structure like this:
state = {
addReducer: {
currentFilter: 'all',
todos: [],
displayed: [],
},
displayReducer: {
currentFilter: 'all',
todos: [],
displayed: [],
},
filterReducer: {
currentFilter: 'all',
todos: [],
displayed: [],
}
}
the way I create the store:
const store = createStore(
combineReducers({addReducer, displayReducer, filterReducer}),
applyMiddleware(thunk, logger)
);
The problem is when an action got dispatched, the state 'slice' that got updated is only the part associated with the reducer handling that action (in this case below, only the slice on addReducer got updated.
The way I write my reducers is only exporting 1 reduce function for each reducer file.
Is there any workaround to handle this, or a better and more correct way to deal with multiple reducers, so that the 'single source of truth' concept really happens on my Redux store(instead of different source for different reducer)? Thanks!
EDIT: one of my reducers(the other two are very similar to this one, accepting only 1 switch case and a default case)
export default function reduce(state = initialState, action) {
switch (action.type) {
case types.ADD_TODO:
let newTodos = [...state.todos, action.data.todo];
let newDisplayed = [...state.displayed];
if (state.currentFilter !== 'completed') {
newDisplayed.push(action.data.todo);
}
console.log(Object.assign(
{},
state,
{
todos: newTodos,
displayed: newDisplayed,
}
));
console.log('newTodos:', action.data.todo);
return Object.assign(
{},
state,
{
todos: newTodos,
displayed: newDisplayed,
}
);
default:
return state;
}
};
I think you are confusing things, you should only have one reducer not three (in your scenario). You should create different reducers to manage different portions of the state, you shouldn't create different reducers about the same portion of the state just addressing different functionalities. You have action creators for it.
You are breaking the "single source of truth" principle from the beginning. So my suggestion is to have one reducer only in your case.
See the docs for more about this with good examples. LINK
Just the naming suggests confusion, you should have a todoReducer which is associated with the todo resource in your app. The addReducer is associated with a type of action same for the other two, which is not what you want to do.
I hope this clarifies things a bit but when in doubt check the redux docs they are pretty great!

Reusability of reducers at different levels of app state structure

For example we have reducer photos, which handles array of photos via actions ADD_PHOTO and REMOVE_PHOTO. And if we have arrays users and posts, they both have field for array of photos.
So, in order to avoid code duplicates I'm going to do the following:
Create reducer user = combineReducers(..., photos, ...)
Create actionCreator updateUser
const updateUser = (id, subAction) => ({
type: UPDATE_USER,
payload: {
id,
subAction
}
})
Create reducer users (Here I'm using Immutable.js)
function users(state = List(), action) {
switch (action.type) {
//...
case UPDATE_USER:
const { id, subAction } = action.payload
const index = state.findIndex(user => user.id == id)
return state.updateIn(
[index, 'photos'],
state => photos(state, subAction)
)
break
//...
default:
return state
}
}
And then I'm going to use all of it like this:
dispatch(updateUser(id, addPhoto(url)))
Is this a correct solution of my problem?
Why not simply dispatch both in the place where this is initiated by the user?
dispatch(updateUser(id));
dispatch(addPhoto(url));
I haven't come across this pattern you're applying before. It seems a bit unusual that one reducer is responsible for reducing state of another. Creates a dependency between them that doesn't feel very pure. I'm not even sure one reducer should be able to/can see the state of another.
So no idea about "correct", but I'd say it's not ideal to do it your way. I'd try dispatching both sequentially or maybe in a sort of meta-action that takes care of nested updates and dispatches actions to multiple reducers.

How to mount the redux-form further down in a nested state structure?

I am stuck here on how to mount the redux-form reducer at the right place.
I have this initial state for a namespace called person (among many other namespaces in the app), something like this:
{
personList: [],
creatingNewPerson: false,
newPerson: {}
}
the newPerson state is a form. How can I tell to have a redux-reducer acting on the newPerson state alone?
sure, you could do something like
combineReducers({
person: personReducer, // that's a reducer using the above json
newPerson: formReducer // import { reducer as formReducer } from 'redux-form'
})
but that's not the structure I am after. The state for the newPerson will be managed outside of the person state. But I want it to be managed inside.
It should be possible when the states redux-form is managing are JSON serializable.
How can this be achieved? Hope I made myself clear enough?
Unfortunately redux-form seems quite opinionated in this particular case, and the general sentiment from the documentation is quite clear: You should mount the formReducer at the root of your state tree under the form -key.
The reason for this is simple: the formReducer will handle the state for all of your forms, not just the new person form. So the state will look something like this:
{
person: { ... person-related state ... },
form: {
NewPersonForm: { ... new person form state ... },
SomeOtherForm: { ... some other form state ... },
...
NthAdditionalForm: { ... nth additional form state ... }
}
}
This means that if you'd want to position the state for each form nested inside the reducer the resulting object will end up in, you'd have to add in instances of formReducer in multiple locations, which would unnecessarily complicate your state.
My recommendation: Eat your proverbial greens in this case and just insert the formReducer in the default location, because that way you'll get the enjoy the power of redux-form without any additional headaches.
Now, after reading the above, if you're still dead set on actually mounting the formReducer somewhere deep within the damp and dark mazes of your state tree, you could do something like the following:
combineReducers({
persons: combineReducers({
person: personReducer,
newPerson: formReducer
}),
other: otherReducer,
...
some: someReducer
})
Then you also need to pass a getFormState to each reduxForm -wrapped component so they know where you hid their state:
const getFormState = state => {
// return the slice of state where we hid formReducer
return state.persons.newPerson
}
reduxForm({ getFormState })(SomeForm)
Doing this is something I cannot, with good conscience, recommend, but should produce the results you want (in addition to possible nasty side-effects if you ever add more than this one form to your app).
Hope this helps!

Should Actions in Redux always be unique?

In this example I'm using an action named ADD_TODO
import { createStore, combineReducers } from 'redux';
function todos(state, action) {
state = state || [];
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ]);
default:
return state;
}
}
function counter(state, action){
state = state || 0;
switch (action.type){
case 'INCREMENT':
return state+1;
case 'DECREMENT':
return state-1;
case 'ADD_TODO':
return state+100;
default:
return state;
}
}
var app = combineReducers({
todos: todos,
counter: counter
});
var store = createStore(app);
store.dispatch({ type: 'ADD_TODO': text: 'buy eggs' });
This cause both the "todos" and "counter" reducers to trigger.
Should I make all reducers have unique actions unless I actually intended it?
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
Should name spacing actions solve it?
eg: "POINT_INCREMENT", "POINT_DECREMENT".
There's nothing inherently wrong with having different reducers respond to the same action -- for example, if you refresh the entire state at once. But yeah, if you have two counters that correspond to different things, you probably want to come up with a naming scheme to differentiate. But I would think the action names probably should have some noun to indicate what they apply to.
This cause both the "todos" and "counter" reducers to trigger. Should I make all reducers have unique actions unless I actually intended it?
Yes, probably they should have different unique actions.
From your example it becomes not really clear what you actually intend.
Should the counter count the amount of todo's ?
In that case it can actually be sensible that a "ADD_ITEM" action would both update the counter and also add a todo item. In that case please refer to the answer of acjay
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
When displaying a list of counters in the same app, each counter can be assigned a unique identifier (id).
An action should pass along the id of the counter.
export const toggleTodo = id => ({
type: 'INCREMENT',
id
})
The reducer should then check by id which counter to update.
See this example of a todo list in the official redux docs.
https://redux.js.org/basics/example
Redux actions are in a way globals. There are different strategies to workaround this problem on a larger scale.
https://kickstarter.engineering/namespacing-actions-for-redux-d9b55a88b1b1

Resources