From all the reading i've done, all I can see for updating single properties in a reducer is to create an action for updating that property. Is the accepted pattern really to make an action for every property?
For example, let's say I had a todo reducer with a default state with three different properties. Is the expected pattern to create 3 different update actions, one for each property, like this?
const todo = ({
text: '',
complete: false,
assignedUser: null
}, action) => {
switch(action.type) {
case 'UPDATE_TEXT':
return {...state, text: action.payload}
case 'UPDATE_COMPLETE':
return {...state, complete: action.payload}
case 'UPDATE_ASSIGNEDUSER':
return {...state, assignedUser: action.payload}
default:
return state
}
}
If not a separate update action for each property, then what are people doing as the pattern for updating each property?
It is another way and maybe can help you:
const todo = ({
text: '',
complete: false,
assignedUser: null
}, action) => {
switch(action.type) {
case 'UPDATE':
return {...state, ...action.payload}
default:
return state
}
}
Now, everything you pass by action's payload, will enter into the state, for example:
const updateTodo = payload => ({type: "UPDATE", payload})
dispatch(updateTodo({
complete: true,
text: "Hello world",
assignedUser: {}
}))
Related
I have an exercise app where a user inputs a 'name' and 'weight'. Then they are allowed to update the 'weight' of a specific 'name'. I am having trouble figuring out how to let them do this, and the closest I have gotten so far is the 'weight' of every 'name' getting updating to the exact same number. For example, if I have:
[
{
name: 'bench',
weight: 100
},
name: 'squat',
weight: 200
},
]
and then the user tried to update just the weight of bench to 300, what happens is both bench and squat get updated to 300. I want just the weight of bench to get updated though.
Here is my code so far. First, my actions:
export const addMovement = (formValues) => {
return {
type: constants.ADD_MOVEMENT,
payload: formValues,
}
};
export const updateMovement = (formValues) => {
return {
type: constants.UPDATE_MOVEMENT,
payload: formValues,
}
};
My reducers:
const initialState = [];
const movementReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_MOVEMENT:
return [ ...state, action.payload ];
case UPDATE_MOVEMENT:
return [
...state.map(item => Object.assign({}, item, { movementWeight: action.payload.movementWeight }))
];
default:
return state;
}
};
If more code is needed I will provide it, just ask. Any help or tips would be appreciated.
If you find object for update by "name" field so you can try with:
case UPDATE_MOVEMENT:
return [
...state.map(item => item.name === action.payload ? action.payload : item )
];
I need help whit Angular ngRx store. I spend couple days for this, but I can't figure out((
I have 2 store in my app and when i dispatch one of them (store.dispatch(new LoadProperties() )) my previous value from another store overrated
this my effects, requcers and app.module
recipes.effects.ts
#Effect() loadRecipes$ = this.dataPersistence.fetch(fromRecipes.LOAD_RECIPES, {
run: (action: LoadRecipes, state: RecipesState) => {
return this.recipesService.getRecipes()
.pipe(map((res: Recipe[]) => new LoadRecipesSuccess(res)));
},
onError: (action: LoadRecipes, error) => {
this.toaster.errorSnackBar(error.statusText, 'Cant fetch categories');
return of(new LoadRecipesError(error));
}
});
properties.effects.ts
#Effect() loadProperties$ = this.dataPersistence.fetch(fromProperties.LOAD_PROPERTIES, {
run: (action: LoadProperties, state: PropertiesState) => {
return this.propertiesService.getProperties()
.pipe(map((res: Property[]) => new LoadPropertiesSuccess(res)));
},
onError: (action: LoadProperties, error) => {
this.toaster.errorSnackBar(error.statusText, 'Cant fetch properties');
return of(new LoadPropertiesError(error));
}
});
export interface AppState { recipes: fromRecipes.RecipesState; properties: fromProperties.PropertiesState;
}
imports: [
SharedModule,
BrowserModule.withServerTransition({appId: 'my-app'}),
HttpClientModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
StoreModule.forRoot(fromApp.appReducer),
EffectsModule.forRoot(fromApp.appEffects),
StoreDevtoolsModule.instrument({ maxAge: 10 }),
NxModule.forRoot()
...]
upd
this is recipe reducer
Hey there! recipes reducers
witch (action.type) {
case RecipesActions.LOAD_RECIPES:
return {
...state, // the incoming state
loading: true // turn loading indicator on
};
case RecipesActions.LOAD_RECIPES_SUCCESS:
return {
...state, // the incoming state
recipes: action.payload,
loaded: true, // recipes were loaded
loading: false, // turn loading indicator off
};
case RecipesActions.LOAD_RECIPES_ERROR:
return {
...state, // the incoming state
loaded: false, // the recipes were loaded
loading: false, // turn loading indicator off
};
this is properties.reducer
switch (action.type) {
case PropertiesActions.LOAD_PROPERTIES:
return {
...state, // the incoming state
loading: true // turn loading indicator on
};
case PropertiesActions.LOAD_PROPERTIES_SUCCESS:
return {
...state, // the incoming state
properties: action.payload,
loaded: true, // properties were loaded
loading: false, // turn loading indicator off
};
case PropertiesActions.LOAD_PROPERTIES_ERROR:
return {
...state, // the incoming state
loaded: false, // the properties were loaded
loading: false, // turn loading indicator off
};
Seems like the reducers don't have a default case, which it should have otherwise the state will be undefined.
switch (action.type) {
... other cases ...
default:
return state;
}
When I dispatch an action after dispatching another action, I noticed that my state isFetching becomes undefined. I think it's probably some asynchronous issue with the actions dispatching after the other. How would I fix this so that I can get both actions to dispatch correctly?
My redux module:
const FETCHING = 'FETCHING'
const FETCHING_KEYWORD = 'FETCHING_KEYWORD'
function fetching() {
return {
type: FETCHING,
}
}
function settingKeyWord(keyWord) {
return {
type: FETCHING_KEYWORD,
keyWord,
}
}
export function fetchKeyWord (keyWord) {
return (dispatch, getState) => {
const { isFetching } = getState()
const { keyWord } = getState()
// After I dispatch the two actions here, isFetching becomes undefined
dispatch(fetching())
dispatch(settingKeyWord(keyWord))
}
}
const initialState = {
isFetching: false,
keyWord: '',
}
export default function results(state = initialState, action) {
switch (action.type) {
case FETCHING:
return {
isFetching: true,
}
case FETCHING_KEYWORD:
return {
keyWord: action.keyWord,
}
default:
return state
}
}
The reducer cases need to return the entire state, not just the updated part, so the problem should also occur when dispatching either action normally. You can fix it by using Object.assign or object-spread syntax in the reducer cases. For example, for Fetching:
case FETCHING:
return Object.assign((), state, {
isFetching: true,
})
or
case FETCHING:
return {...state,
isFetching: true,
})
Maybe I'm missing something completely obvious but this has been tripping me up today.
Let's say we have a Redux store with a structure like so:
const state = {
...
pages: {
...
accountPage: {
currentTab: 'dashboard',
fetching: false,
tableSettings: {
sortDir: 'asc',
sortField: 'name'
}
}
}
}
So there is obviously a main reducer...
export default combineReducers({
...
pages: pagesReducer
...
});
Then the reducer for pages has the reducer for each page...
export default combineReducers({
...
accountPage: accountPageReducer
...
});
And now finally we get down to the meat of the problem, the reducer for this particular piece of state.
export default handleActions({
[setCurrentTab]: (state, action) => { ... },
[setIsFetching]: (state, action) => { ... }
});
That's all good right? Well, the key in the state given at the outset at tableSettings should actually be handled by it's own reducer. This pattern may exist many times in the state, so it is abstracted away to a reducer-creating function:
const defaultState = {
sortDir: 'asc',
sortField: null
};
export const createTableSettingReducer (actions, extra ={}) => {
return handleActions({
[actions.changeSortDir]: (state, action) => ({ ...state, sortDir: action.payload }),
[actions.changeSortField]: (state, action) => ({ ...state, sortField: action.payload }),
...extra
}, defaultState)
}
So, above the reducer for the sections of state (accountPageReducer), we created the reducer:
// pretend these actions were imported
const tableSettingsReducer = createTableSettingReducer({
changeSortDir: setSortDir,
changeSortField: setSortField
});
So the question is, where do I put tableSettingsReducer?
This of course, doesn't work:
export default handleActions({
[setCurrentTab]: (state, action) => { ... },
[setIsFetching]: (state, action) => { ... },
tableSettings: tableSettingsReducer
});
It doesn't work because handleActions expects to use the action constants as keys, not the actual key in the state.
There is also nowhere to use combineReducers, since there is only one nested reducer of this slice of state. currentTab and fetching do not need their own reducer, so it's fruitless to use combineReducers.
I know that recently redux-actions started support nested reducers...but there isn't really any documentation available showing exactly how it's supposed to be done, or even describing the parameters needed to make it happen.
I could possibly use combineActions, and combine all of the actions in handleActions for every action that can be taken by a nested reducer. But that doesn't seem very clean...plus, what if the nested reducer has it's own nested reducers? That means every time those reducers can process a new action, that action needs to be added to combineActions in all its parents. Not the best.
Thoughts?
Every key in your state gets its own reducer. Some reducers are really simple, some are themselves composed of other reducers. All the sister keys at each level of your state tree can be combined with combineReducers.
const initialCurrentTab = 'dashboard';
const currentTabReducer = handleActions({
[setCurrentTab]: (state, action) => {
return action.payload;
},
}, initialCurrentTab);
const defaultFetchingState = false;
const fetchingReducer = handleActions({
[setIsFetching]: (state, action) => {
return action.payload;
},
}, defaultFetchingState);
export default combineReducers({
currentTab: currentTabReducer,
fetching: fetchingReducer,
tableSettings: tableSettingsReducer,
});
let say you have the initialState = { data : []}
let assume that the upcoming action has payload of an array
export the reducer as the following :
return handleActions({
["Action Type 1" ]: (state, { payload }) => {
return { ...state, data: [...state.data, ...payload ]} ;
},
["Action Type 1" ]: (state, { payload }) => {
return { ...state, data: [...state.data, ...payload ]} ;
},
}, initialSate );
import this reducer in your combine reducer .
My question relates to redux and more specifically how to handle errors/failures from within reducer functions. I am in reference to the ngrx example app (https://github.com/ngrx/example-app) and the way it handle errors/failures.
Here is the reducer function I am referring to:
export function reducer(state = initialState, action: collection.Actions): State {
switch (action.type) {
case collection.ActionTypes.LOAD: {
return Object.assign({}, state, {
loading: true
});
}
case collection.ActionTypes.LOAD_SUCCESS: {
const books = action.payload;
return {
loaded: true,
loading: false,
ids: books.map(book => book.id)
};
}
case collection.ActionTypes.ADD_BOOK_SUCCESS:
case collection.ActionTypes.REMOVE_BOOK_FAIL: {
const book = action.payload;
if (state.ids.indexOf(book.id) > -1) {
return state;
}
return Object.assign({}, state, {
ids: [ ...state.ids, book.id ]
});
}
case collection.ActionTypes.REMOVE_BOOK_SUCCESS:
case collection.ActionTypes.ADD_BOOK_FAIL: {
const book = action.payload;
return Object.assign({}, state, {
ids: state.ids.filter(id => id !== book.id)
});
}
default: {
return state;
}
}
}
Can someone please explain the necessity for dealing with those two actions from within the reducer function:
REMOVE_BOOK_FAIL
ADD_BOOK_FAIL
For instance why remove the book from the state (in the case of the ADD_BOOK_FAIL action)?
If the add book action has failed, then the book is not present in the store. Is it?
Maybe it's the naming used that makes it a red herring, my guess is that ADD_BOOK_FAIL could be in use somewhere else for a different use case as a fall back mechanism.
I agree the way you describe it this doesnt make sense the developer did it for this reason.