my current state is an object like this
{active: true, dateAdded: "02-07-2019", endEpoch: "02-07-2019", name: "pics"}
My reducer
export default (state = initial_state, actions) => {
switch (actions.type) {
case SUBREDDIT_SELECTED:
{
return { ...state,
selected_sub: actions.payload
}
}
break;
case EPOCH_CHANGED:
{
return { ...state,
selected_sub: actions.payload
}
}
break;
default:
return state;
}
}
How do I update the endEpoch property of the state?
You already do what you want. To update endEpoch do
case EPOCH_CHANGED:
{
return { ...state,
endEpoch: actions.payload
}
}
break;
Spreading state leaves all it props in place. And changes endEpoch.
Related
I have a Redux reducer which handles among other things products. Each product has an array field named productImages. An action type DELETE_PRODUCT_IMAGE_SUCCESS removes a specific image from that array.
How can I automatically delete a product once all its productImages are removed?
I've tried using useEffect to no avail.
My codesandbox is available here.
case appConstants.DELETE_PRODUCT_IF_NO_IMAGE:
return {
...state,
products: state.products.filter(
(product) => product?.productImages?.length > 0
)
};
You could update the list when you delete an image from the product.
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
products: state?.products
.map((item, index) => {
if (index !== action.payload?.productIndex) return item;
return {
...item,
productImages: item?.productImages.filter(
({ imageFileName = null }) =>
imageFileName !== action?.payload?.imageFileName
)
};
})
.filter((product) => product?.productImages?.length > 0)
};
case appConstants.DELETE_PRODUCT_IF_NO_IMAGE:
return state;
It seems you are calling the purge method only once when the app loads:
useEffect(() => {
dispatch(deleteProductIfNoImage());
}, [dispatch]);
A more efficient way would be to introduce sub-reducers, it would make your task easier:
const product = (state, action) => {
const { productImages = [] } = state;
const { imageFileName } = action.payload || {};
switch (action.type) {
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
productImages: productImages.filter(
(productImage) =>
productImage.imageFileName !== imageFileName
),
};
default:
return state;
}
};
const products = (state, action) => {
const { productIndex } = action.payload || {};
switch (action.type) {
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
products: products
.map((item, index) =>
index === productIndex ? product(item) : item
)
.filter((item) => item.productImage?.length > 0),
};
default:
return state;
}
};
const appReducer = (state, action) => {
switch (action.type) {
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
products: products(state, action),
};
default:
return state;
}
};
If you follow this sub-reducers advice which Dan Abramov suggested in his online redux course + switch to dictionaries and product ids, the reducer code would be much cleaner and possibly more maintainable:
const product = (state, action) => {
switch (action.type) {
case appConstants.CHANGE_PRODUCT_CODE:
return {
...state,
newProductCode: action.payload.productCode,
};
case appConstants.CHANGE_PRODUCT_NAME:
return {
...state,
productName: action.payload.productCode,
};
case appConstants.CHANGE_PRODUCT_CATEGORY:
return {
...state,
productName: action.payload.productCategory,
};
}
};
const products = (state = initialState, action) => {
switch (action.type) {
case appConstants.CHANGE_PRODUCT_CODE:
case appConstants.CHANGE_PRODUCT_NAME:
case appConstants.CHANGE_PRODUCT_CATEGORY:
return {
...state,
products: {
...state,
[action.id]: product(products[action.id], action)
}
}
}
};
Furthermore, I recommend checking out redux toolkit which is the new redux standard for building stores and perhaps utilizing immerJs instead of sub-reducers in your redux reducer code.
I can compose state as follows with combineReducers with a reducer function handling each part of the state (foo and bar):
function foo(state, action) {
switch(action.type) {
case types.A:
return action.value;
default:
state;
}
}
function bar(state, action) {
switch(action.type) {
case types.B:
return action.value;
default:
return state;
}
}
combineReducers({ foo, bar });
Or have case reducer functions that correspond to actions, which take the whole state and set just one key-value on them. (Could be using immutable.js as below or just plain ojects)
function a(state, value) {
state.set('foo', action.value);
}
function b(state, value) {
state.set('bar', action.value);
}
function reducer(state = Map()) {
switch (action.type) {
case types.A:
return a(state, action.value);
case types.B:
return b(state, action.value);
}
}
Are there certain advantages of one style over the other?
I have a redux app which enables users to post something(like a blog post which contains only a title and body).The user can edit the post whenever they want to.I am not able to update the reducer,i.e. I just want to update the title and the body for that particular "id". I am trying to update the state in my reducer as shown below:
case SUBMIT_POST:
let id = action.payload.id;
return {
...state,
[id]: {
...state[id],
title: action.payload.title,
body: action.payload.body
}
};
My action-creator looks like this:
//Action creator for submitting edited post
export function submitEditedPost(id, values, callback) {
const request = axios.put(`${API}/posts/${id}`, {values}, {headers});
return dispatch => {
return request.then((res) => {
callback();
console.log(res.data.values)
dispatch({
type:SUBMIT_POST,
payload: res.data.values
})
})
}
}
How to update the reducer with the new edited title and body for that particular post id?
EDIT 1:
I am calling the action creator in my form onSubmit() method as shown below:
onSubmit(values) {
const { id } = this.props.match.params;
const formData = {};
for (const field in this.refs) {
formData[field] = this.refs[field].value;
}
formData.id = id;
console.log('-->', formData);
this.props.submitEditedPost(id, formData, () => {
this.props.history.push('/');
});
}
EDIT 2: Screenshot of the action in reducer is shown below:
EDIT 3: My entire reducer:
import _ from 'lodash';
import { FETCH_POSTS, FETCH_POST, CREATE_POST, EDIT_POST, SUBMIT_POST } from '../actions/posts_action';
export default function(state = {posts: {} }, action) {
switch (action.type) {
case FETCH_POST:
// const post = action.payload.data;
// const newState = { ...state, };
// newState[post.id] = post;
// return newState;
return {...state, [action.payload.id]: action.payload};
case FETCH_POSTS:
return {posts: { ...state.posts, ...(_.mapKeys(action.payload,'id'))}};
case CREATE_POST:
return {...state, [ action.payload.id]: action.payload};
case EDIT_POST:
return { ...state, [action.payload.id]: action.payload};
case SUBMIT_POST:
console.log(action.payload);
let id = action.payload.id;
return {
...state,
[id]: {
...state[id],
title: action.payload.title,
body: action.payload.body
}
};
default:
return state;
}
}
EDIT 4 : Screenshot of redux-dev-tools:
I'm writing a redux reducer to delete a key from a state object:
state = {
items: {
key1: {foo: 'bar'},
key2: {foo: 'baz'}
},
someOtherKey: 'value'
}
My reducer:
function reducer(state, action) {
if (action=='DEL') {
return {
...state,
items: {
...state.items,
[action.key]: undefined
}
}
}
}
I expected that this would return a new state with the respective property deleted, but instead it returns a new state with the key still present in the object but having the value undefined.
Is there an elegant way in ES6 to write such a reducer? I guess I could use Object.assign and delete the property, but the above pattern is so much more expressive, so I'd like to write it this way if possible.
I ended up using the lodash function omit:
import { omit } from 'lodash'
function reducer(state, action) {
if (action=='DEL') {
return {
...state,
items: omit(state.items, action.key)
}
}
}
}
The fact that the lodash library contains such a function leads me to the assumption that there is probably no easier way to express the operation in simple JavaScript. If I'm wrong, let me know; I'd be very interested to hear.
Untested, what about this? It will create a shallow copy (immutable) of each item, except the one you don't care for.
function reducer(state, action) {
if (action == 'DEL') {
return {
...state,
items: Object.keys(state.items).reduce((obj, key) => {
if (key !== action.key) {
obj[key] = { ...state.items[key]
}
}
}, {})
}
}
}
Another technique: copy the object then delete the key:
function reducer(state, action) {
if (action=='DEL') {
let newItems = {...state.items}; // Copy the object
delete newItems[action.key]; // Remove key from the copy
return {
...state,
items: newItems,
};
}
}
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.