React/Redux: adding multiple objects to redux state with lodash mapKeys() - redux

I am mapping over Posts and Comments ( both in a structure of an object containing indexed objects ).
Now - I am successfully adding a single Comment to state like this:
case FETCH_COMMENT_FULFILLED: {
return {
...state,
fetching: false,
fetched: true,
comments: {
...state.comments,
[action.payload.id]: action.payload,
}
}
}
And I am successfully adding multiple Comments related to a post to state like this:
case FETCH_COMMENTS_FULFILLED: {
return {
...state,
fetching: false,
fetched: true,
comments: _.mapKeys(action.payload, 'id')
};
}
But on the main page when I am mapping over multiple Posts and am fetching their respective Comments, naturally the Comments in state are overridden.
I guess I would have to do something along the lines of:
case FETCH_COMMENTS_FULFILLED: {
return {
...state,
fetching: false,
fetched: true,
comments: {
...state.comments,
_.mapKeys(action.payload, 'id')
}
}
}
However this syntactically does not work. There is an 'unexpected Token' error.
Can anyone give me a hint what the correct way would be?
Thanks,
Jan

Related

how to update a specific property in a state using ngrx

how do i update a specific property of object in a state?
i need to update the property offerPrice but still want to retain the other state values in the object
this is what i have in my effects
case OffersActionTypes.SET_TOTAL_OFFER_PRICE:
return {
...state, offerDetails: {
...state, singleOfferDetails: [
...state.offerPrice, action.payload
]
}}
but on the redux tools it giving me this
If you look carefully, you're not copying the parts of state that need to be copied. I believe you want something more like:
return {
...state,
offerDetails: {
...state.offerDetails,
singleOfferDetails: {
...state.offerDetails.singleOfferDetails,
offerPrice: action.payload
}
}
}
Of course, this can also be achieved with something like:
const newState = { ...state };
newState.offerDetails.singleOfferDetails.offerPrice = action.payload;
return newState;
I would argue, however, that your store is too embedded. Generally you want to keep things as flat as possible, although I'll grant you that it's not always a simple process. Best of luck!

Redux reducer design for large state within a single module

I have come across two kinds of reducer design for handling a large state within a single module.
The first approach is to have all the variables inside a single large state and have one reducer function.
const initialState = {
results: [],
pagination: {},
filters: [],
appliedFilters = [],
}
const reducer = (st = { ...initialState }, action) => {
const state = st;
switch (action.type) {
case 'SEARCH':{
return {
...state,
results: action.results,
pagination: action.pagination,
filters: action.filters,
appliedFilters: action.appliedFilters
},
case 'APPLY_FILTER':{
return {
...state,
results: action.results,
pagination: action.pagination,
filters: action.filters,
appliedFilters: action.appliedFilters
},
case 'PAGINATE':{
return {
...state,
results: action.results,
pagination: action.pagination,
}
}
The second approach is to have multiple reducers for the sub items in the data.
export function applications(state = [], { type, results}) {
switch (type) {
case SEARCH:
return results;
case INIT_RESULTS:
return [];
default:
return state;
}
}
export function pagination(state = null, { type, paginationData }) {
switch (type) {
case SEARCH:
return paginationData;
default:
return state;
}
}
export function filters(state = [], { type, filterData }) {
switch (type) {
case SEARCH:
return filterData;
case UPDATE_FILTERS:
return filterData;
default:
return state;
}
}
I think both have their own pros and cons. Considering scalability and modularization which one is a better pick?
Generally, both of these are very far off our official recommendations.
you should have a "slice" reducer for each sub-state (that rules out your first option
you should not treat reducers as "setting a value", but move the whole "calculating how to get the value" into the reducer and handle your action as just "describing an event that happened"
you should be using the official Redux Toolkit which we are recommending & teaching as the default way of writing Redux sinde 2019. Seriously, look at it. It is about 1/4 of the code. No more switch..case reducers or ACTION_TYPES.
Please give the Redux Style Guide a read and to learn modern Redux with Redux Toolkit, please follow the official Redux Tutorial

How do I pass a new key as an array with immer?

Say I have some initial state like
const initialState = {
loading: false,
updating: false,
saving: false,
data: {},
error: null
};
And I want add to data as the result of an action but the data I want to add is going to be an array. How do I go about this?
I've tried
export default produce((draft, action) => {
switch (action.type) {
case UPDATE_STATE.SUCCESS:
draft.data.new_Array.push(action.payload);
draft.loading = false;
break;
default:
}
}, initialState);
but this errors.
If I start the initial state as
const initialState = {
loading: false,
updating: false,
saving: false,
data: {
newArray: []
},
error: null
};
any update to the state before I make the array key overwrites the initial state and removes the key. ie
export default produce((draft, action) => {
switch (action.type) {
case OTHER_UPDATE_STATE.SUCCESS:
draft.data = payload.action;
draft.loading = false;
break;
default:
}
}, initialState);
can anyone help?
There's one thing I noticed you'll run into trouble, draft.data.new_Array.push(action.payload);
Make sure you don't modify the existing data structure. The reason is that redux relies on the memory reference of an object, if the object reference doesn't change, it might fool the redux that nothing actually happened in the past.
In your case, i have a feeling that nothing will ever get triggered.
One way to modify the reference is to create an new object, ex.
return [...data, newElementObject]

Reducer checking action.type with indexOf

I wrote a redux reducer that helps me group error messages from other reducers.
I'm wondering if this could have any side effect since I don't see anyone doing it. I'm also wondering if there is a better way to do it that I couldn't think of.
This is what I wrote:
const errors = (state = {}, action = {}) => {
let new_state = Object.assign({}, state);
// if action type contains ERROR and action error is present
if (action.type.indexOf("ERROR") != "-1" && action.error) {
let error_id = Utils.hashCode(action.error);
// if error already in the array
if (new_state[error_id]) {
new_state[error_id].count++;
}
// otherwise add the message to the list
else {
new_state[error_id] = {message: action.error, count: 1};
}
}
// regular switch stmt
switch (action.type) {
case ERRORS_RESET: new_state = {}; break;
}
return new_state;
}
My store looks something like this now:
{
reducer1: {
something: [],
error: "Some error message",
},
reducer2: {
something: [],
error: false,
},
reducer3: {
some_other_obj: {},
error: "Another error message",
},
errors: [
{message: "Some error message, count: 1}
{message: "Another error message", count: 2}
]
}
The overall concept of listening for "SOMETHING_ERROR" actions is fine, but there's a couple issues with your implementation.
First, your if statement has a direct mutation of the existing state. Per the Structuring Reducers - Immutable Update Patterns section of the Redux docs, you need to make sure you copy every level of nesting. Right now you're copying the first level of the state, but not the nested object.
Second, you're always copying the state, even when there's nothing that's actually changed. That's generally going to cause unnecessary re-renders in your UI.

redux-promise with Axios, and how do deal with errors?

So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..
in action/
const request = axios(SOME_URL);
return {
type: GET_ME_STUFF,
payload: request
}
in reducer/
const startState = {
whatever: [],
error: false
}
case GET_ME_STUFF:
return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}
etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.
I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.
So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.
It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.
In your actions file, add additional action creators that would handle the success and error (and any other) states.
function getStuffSuccess(response) {
return {
type: GET_ME_STUFF_SUCCESS,
payload: response
}
}
function getStuffError(err) {
return {
type: GET_ME_STUFF_ERROR,
payload: err
}
}
export function getStuff() {
return function(dispatch) {
axios.get(SOME_URL)
.then((response) => {
dispatch(getStuffSuccess(response))
})
.catch((err) => {
dispatch(getStuffError(err))
})
}
}
return null
This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.
The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.
In the reducer you should inspect the existence of the error attribute on the action object:
// This is the reducer
export default function(previousState = null, action) {
if (action.error) {
action.type = 'HANDLE_XHR_ERROR'; // change the type
}
switch(action.type) {
...
And change the type of the action, triggering a state change for an error handling component that you have set up for this.
You can read a bit more about it here on github.
It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.
store.dispatch (function (dispatch) {
dispatch ({
type:'FOO',
payload:axios.get(url)
})
.catch (function(err) {
dispatch ({
type:"FOO" + "_REJECTED",
payload:err
});
});
});
and in the reducer
const reducer = (state=initialState, action) => {
switch (action.type) {
case "FOO_PENDING": {
return {...state, fetching: true};
}
case "FOO_REJECTED": {
return {...state, fetching: false, error: action.payload};
}
case "FOO_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
data: action.payload,
};
}
}
return state;
};
Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.
First, set a property in the redux state that will hold any ajax errors that may occurred.
ajaxError: {},
Second, setup a reducer to handle ajax errors:
export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
if (action.error) {
const { response } = action.payload;
return {
status: response.status,
statusText: response.statusText,
message: response.data.message,
stack: response.data.stack,
};
}
return state;
}
Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Alert from 'react-s-alert';
class AjaxErrorsHandler extends Component {
constructor(props, context) {
super(props, context);
this.STATUS_GATE_WAY_TIMEOUT = 504;
this.STATUS_SERVICE_UNAVAILABLE = 503;
}
componentWillReceiveProps(nextProps) {
if (this.props.ajaxError !== nextProps.ajaxError) {
this.showErrors(nextProps.ajaxError);
}
}
showErrors(ajaxError) {
if (!ajaxError.status) {
return;
}
Alert.error(this.getErrorComponent(ajaxError), {
position: 'top-right',
effect: 'jelly',
timeout: 'none',
});
}
getErrorComponent(ajaxError) {
let customMessage;
if (
ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
) {
customMessage = 'The server is unavailable. It will be restored very shortly';
}
return (
<div>
<h3>{ajaxError.statusText}</h3>
<h5>{customMessage ? customMessage : ajaxError.message}</h5>
</div>
);
}
render() {
return (
<div />
);
}
}
AjaxErrorsHandler.defaultProps = {
ajaxError: {},
};
AjaxErrorsHandler.propTypes = {
ajaxError: PropTypes.object.isRequired,
};
function mapStateToProps(reduxState) {
return {
ajaxError: reduxState.ajaxError,
};
}
export default connect(mapStateToProps, null)(AjaxErrorsHandler);
You can include this component in your App component.
This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.
export function updateJob(payload, context){
const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
context.successHdl(response);
})
.catch(function (error) {
context.errorHdl(error);
});;
return {
type: UPDATE_SOMETHING,
payload: payload,
}
}
Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.
Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html
It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.

Resources