how to push into nested array in reducer - javascript

Above image shows what happens when I trying to dispatch a reply action.
Currently trying to push the input field value etc into the replyComments...but it is still empty even when dispatching this action.... the reducer is checking the majorIndex first and then the minorIndex, from there it should push it into the replyComments. Any one knows how I can fix this?
Link to working Codesandbox
case types.REPLY_COMMENT: {
return {
...state,
enableToggleComment: true,
imageData: state.imageData.map(image => image.id === action.majorIndex ?
{
...image,
comments: {
[action.minorIndex]: {
replyComments: [...image.comments.replyComments, { comment: action.newComment, likeComment: image.toggle, enableReply: false }]
}
}
} : image
)
}
}

EDIT:
I know noticed it's due to a missing key to [action.minorIndex] and when using the ? operator and want to return an object you should add the parenthesis, otherwise you are declaring a scope (like i did whoops)
case types.REPLY_COMMENT: {
return {
...state,
enableToggleComment: true,
imageData: state.imageData.map(image => image.id === action.majorIndex ?
({
...image,
comments: {
[action.minorIndex]: {
replyComments: [...image.comments[action.minorIndex].replyComments, { comment: action.newComment, likeComment: image.toggle, enableReply: false }],
}
}
}) : image
)
}
}

I've only added action.replyText but you should be able to add the rest now
case types.REPLY_COMMENT: {
return {
...state,
enableToggleComment: true,
imageData: state.imageData.map(image =>
image.id === action.generalIndex
? {
...image,
comments: image.comments.map((comment, i) =>
i === action.majorIndex
? {
...comment,
replyComments: [
...comment.replyComments,
action.replyText
]
}
: comment
)
}
: image
)
};
}

Related

no-nested-ternary on a nested and complicated array of objects

I have a complicated array of objects and I want to change a property that deeply nested in the array.
I did the following function and it is work, but the problem is that the eslint throw me an error because there is a nested ternery to check if i am on the currect place in an array:
toggleSwitchDetector(state: Array<IOpticalHeadUnit>, { payload }: any) {
return [
...state.map((opticalHeadUnit) =>
opticalHeadUnit.name === payload.opticalHeadUnitName
? {
...opticalHeadUnit,
detectors: [
...opticalHeadUnit.detectors.map(
(detector, index) => ({
...detector,
status: {
...detector.status,
value: //Nested ternery. Bad practice.
index === payload.index
? detector.status
.value === 0
? 1
: 0
: detector.status.value,
},
}),
),
],
}
: opticalHeadUnit,
),
];
}
is there a simpler way to approach the modification of the deeply nested property?
EDIT:
the code after I modified it with the answers:
const getDetectorStatusValue=(index:number,payload:number,detector:IDetector)=>{
if(index === payload)
{
if(detector.status.value===0)
return 1
return 0
}
return detector.status.value
}
toggleSwitchDetector(state: Array<IOpticalHeadUnit>, { payload }: any) {
return state.map((opticalHeadUnit) =>
opticalHeadUnit.name === payload.opticalHeadUnitName
? {
...opticalHeadUnit,
detectors: [
...opticalHeadUnit.detectors.map(
(detector,index) => ({
...detector,
status: {
...detector.status,
value:getDetectorStatusValue(index,payload.index,detector)
},
}),
),
],
}
: opticalHeadUnit,
);
Maybe something like this
toggleSwitchDetector(state: Array<IOpticalHeadUnit>, { payload }: any) {
return state.map((opticalHeadUnit) => getState(payload, opticalHeadUnit)
}
getStatus(payload, detector) {
return {
...detector.status,
value: //Nested ternery. Bad practice.
index === payload.index
? detector.status
.value === 0
? 1
: 0
: detector.status.value,
};
}
getDetectors(payload, opticalHeadUnit) {
return opticalHeadUnit.detectors.map(
(detector, index) => ({
...detector,
status: getStatus(payload, detector),
}),
);
}
getState(payload, opticalHeadUnit) {
opticalHeadUnit.name === payload.opticalHeadUnitName
? {
...opticalHeadUnit,
detectors: getDetectors(payload, opticalHeadUnit);
}
: opticalHeadUnit,
}
you don't need [...array], replace it with array
If you want to get rid of the nested ternary operator, you could write slightly more code but it will be more readable.
You can check if index === payload.index with a simple if statement and only in that case go deep in your object to eventually check detector.status.value === 0 ? 1 : 0
Otherwise just return detector as is;
...opticalHeadUnit.detectors.map(
(detector, index) => ({
if (index === payload.index) {
return {
...detector,
status: {
...detector.status,
value: detector.status.value === 0 ? 1 : 0
},
}
} else {
return detector;
}
}),
),

Update specific state in Redux with reducer

I would like to know, how can I update a specific value in state. I have my state config, and I would like to update the 'offset' value without modify the limit or total value, just set a new offset with this.props.setParams()...
With this function, I would like to modify offset this.props.setParams({ offset: 10}), or limit this.props.setParams({ limit: 40}), etc... just send a params and modify directly the associate state.
searchList.js
this.props.setParams({ offset: 10})
actions-search.js
export function setFilters(newFilters) {
return {
type : SEARCH.SET_NEW_FILTERS,
payload: newFilters
};
}
export function setParams(newParams) {
return {
type : SEARCH.SET_NEW_PARAMS,
payload: newParams
};
}
reducer-search.js
...
/*State : CONFIG*/
/*config: {
filters : {
...
}
params: {
offset: 0,
limit: 25,
total: 100
}
}*/
case SEARCH.SET_NEW_FILTERS :
let newFilters = action.payload;
return {
...state,
config: {
...state.config,
filters : {
...state.config.filters,
newFilters
}
params : {
...state.config.params
}
}
};
case SEARCH.SET_NEW_PARAMS :
let newParams = action.payload; // {offset: 10}
return {
...state,
config: {
...state.config,
filters : {
...state.config.filters
}
params : {
...state.config.params,
newParams
}
}
};
....
Curently, I must write this :
return {
...state,
config: {
...state.config,
params : {
...state.config.params,
offset : newParams.offset
}
}
};
How can I have more "generic" in my reducer ? No specify a specific property,but more global.
Just send an object "params" in my action creator, example : this.props.setParams({offset: 30, total: 120}) ?
Actual behavior
Your code is adding newFilters and newParams as properties. That way you would have to access offset as:
state.config.params.newParams.offset
Solution
To access those new filters or params directly with
state.config.params.offset
You should spread newFilters and newParams, using the spread syntax ..., in your reducers:
filters : {
...state.config.filters,
...newFilters
}
and
params : {
...state.config.params,
...newParams
}
The spread syntax will add (or replace) the inner properties of your payload, newFilters and newParams, to the properties of state.config.filters and state.config.params respectively.
Improvements
To simplify your reducer you could spread action.payload directly and remove some probably unnecessary spreads:
actions-search.js
...
case SEARCH.SET_NEW_FILTERS :
return {
...state,
config: {
...state.config,
filters : {
...state.config.filters,
...action.payload
}
}
};
case SEARCH.SET_NEW_PARAMS :
return {
...state,
config: {
...state.config,
params : {
...state.config.params,
...action.payload
}
}
};
...

react setState array of object using map doesn't work?

I have problem doing a setState changing value of a nested array of object. Below code suppose
to change question of id 2 to answer: true but it did not, what's wrong?
this.state = {
questions: [
{
id: 1,
answer: ''
},
{
id: 2,
answer: ''
},
]
}
//I have a click event somewhere
this.setState(
{
questions: this.state.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
}
} else {
return { ...q }
}
})
},
console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
)
The console.log(this.state.questions[1]) line is executed before the this.setState line is executed, that's why the old state is printed to the console. You should put the line inside a function to delay the execution:
this.setState(..., () => console.log(this.state.questions[1]));
Also it is recommended to use a function as the first argument if the changed state is derived from the current state because React doesn't apply the new state immediately therefore this.state can be outdated when React applies the new state:
this.setState(state => ({
questions: state.questions.map(q => {
if (q.id === 2) {
return {...q, answer: true};
}
return q;
})
}), () => {
console.log(this.state.questions[1]);
});
You are not invoking your setState callback. Try like this:
this.setState(
{
questions: this.state.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
};
}
return { ...q };
})
},
() => console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
);
Though, since you are using the current state to update your state again, it would be better to use functional setState.
this.setState(
currentState => ({
questions: currentState.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
};
}
return { ...q };
})
}),
() => console.log(this.state.questions[1])
);
Also, you don't have to log your state in a callback to setState. You can log your state in your render method without struggling setState's callback.
this.setState(
currentState => ({
questions: currentState.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
};
}
return { ...q };
})
})
);
....
render() {
console.log( this.state );
....
}
I think it's because Array.map returns an array. Try:
this.setState(
{
questions: this.state.questions.map(q => {
if (q.id === 2) {
q.answer = true;
}
return q;
})
},
console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
)

How can I disable checkboxes and radio buttons from separate components?

Running into this ReactJS (with Redux) issue:
If premium white isn’t selected, gloss finish should be disabled. The radio button (premium) and checkbox (gloss) have separate methods in separate components – looks like they are both using state to send data.
Here’s the checkbox
buildCheckbox(item) {
return (
<Checkbox
key={item.key}
label={item.display}
name={item.key}
checked={this.props.order[item.key] || false}
onChange={checked => this.handleCheck(checked, item.key)}
disabled={item.disabled(this.props.order)}
/>
);
}
And the handleclick method used
handleCheck(checked, key) {
const { params, updateOrder } = this.props;
const { sessionId } = params;
// if doulbeSided option is removed, then clear the inside file.
if (key === 'doubleSided' && !checked) {
updateOrder(sessionId, { inside: null });
}
// set ink coverage based on printed flag
if (key === 'printed') {
const inkCoverage = checked ? 100 : 0;
updateOrder(sessionId, { inkCoverage });
}
// if unprinted, remove doublesided and gloss options
if (key === 'printed' && !checked) {
updateOrder(sessionId, { doubleSided: false });
updateOrder(sessionId, { gloss: false });
}
updateOrder(sessionId, { [key]: checked });
}
And the radio button’s method
onClick(id, ordAttribute) {
const { updateOrder, sessionId, validator } = this.props;
updateOrder(sessionId, { [ordAttribute]: id });
if (validator) validator(ordAttribute);
}
I saw that gloss has a service which is toggling disabled or not via the printed key on state here
gloss: {
display: 'Gloss Finish',
key: 'gloss',
component: 'checkbox',
disabled: state => !state.printed,
},
I’ve thought about creating a fourth radio button and just deleting the gloss option but I’m not sure where it’s being populated from – also thought about using a display none on the styles of the gloss that is activated via the radio button – but am not sure where to start.
just stated a new job and this is the previous employee's code - trying to figure it out. looks like the state is activated via this Action method:
export const updateOrder = (sessionId, payload) => (dispatch, getState) => {
dispatch(updateAction({ ...payload }));
const state = getState();
const ord = getNewOrderForm(state);
const minOrdValue = getMinOrdValue(state);
const { length, width, height, style, blankLength, blankWidth, qty, leadTime, sqFeet } = ord;
const priceMatrix = style ? getPriceMatrix(state)[style.priceMatrix] : null;
if (priceMatrix && style && style.calcPrice) {
dispatch(dispatchNewPrice(ord, style, priceMatrix, minOrdValue));
}
if (shouldCalcBlank({width, length, height}, style)) {
calcBlanks(style, {width, length, height})
.then(blanks => dispatch(updateAction(blanks)))
.catch(err => console.log('error', err))
}
if (blankLength && blankWidth && qty) {
calcSquareFeet({ blankLength, blankWidth, qty })
.then(sqFeet => {
dispatch(updateAction({ sqFeet }));
return sqFeet;
})
.then(sqFeet => sqFeet > 1000)
.then(lrgSqFeet => {
dispatch(updateAction({ lrgSqFeet }));
return lrgSqFeet;
})
.then(lrgSqFeet => {
if (lrgSqFeet && leadTime === 'rush') {
dispatch(updateAction({ leadTime: 'standard' }));
}
});
}
if (sqFeet && (!blankLength || !blankWidth || !qty)) {
dispatch(updateAction({ sqFeet: 0 }));
}
localStorage.setItem(sessionId, JSON.stringify(getNewOrderForm(getState())));
};
i thought about adding a the radio button has an id of 'clearwater' so i thought about adding a bool to this method that could then be accessed as clearwater: false (and when onClick is activated, updateOrder then changes it to clearwater: true, and then the gloss object in the service would then check disabled: state => !state.printed && !state.clearwater (this didn't work):
export const generateNewOrder = (userid, style, sessionId = uuid()) => dispatch => {
localStorage.setItem(
sessionId,
JSON.stringify({
userid,
style,
sessionId,
blindShip: true,
inkCoverage: '100',
printed: true,
})
);
history.push(`/order/new/${style.styleCode}/${sessionId}`);
dispatch(
newOrder({
userid,
style,
sessionId,
blindShip: true,
inkCoverage: '100',
printed: true,
})
);
if (style.type === 'static') {
const { dims, blankLength, blankWidth } = style;
const payload = {
...dims,
blankLength,
blankWidth,
};
dispatch(updateOrder(sessionId, payload));
}
};
I was hoping by changing the Service attached to the checkbox, I could add an additional condition that would cause the disabled functionality to be dependent on the state.boardStyle, but this doesn't seem to work (picture below isn't accurate, i changed it to boardStyle):
http://oi65.tinypic.com/wknzls.jpg
This is using redux -- kind of new to redux -- let me know if I'm missing any info -- I will post anything to get this solved.
Any help would be huge – thanks so much!
i think i figured it out . . .
there's probably a drier way to do this, but here goes:
first i created a new key [bool] (clearwater, set to false) on generateNewOrder method in Actions:
export const generateNewOrder = (userid, style, sessionId = uuid()) => dispatch => {
localStorage.setItem(
sessionId,
JSON.stringify({
userid,
style,
sessionId,
blindShip: true,
inkCoverage: '100',
printed: true,
clearwater: false,
})
);
history.push(`/order/new/${style.styleCode}/${sessionId}`);
dispatch(
newOrder({
userid,
style,
sessionId,
blindShip: true,
inkCoverage: '100',
printed: true,
clearwater: false
})
);
if (style.type === 'static') {
const { dims, blankLength, blankWidth } = style;
const payload = {
...dims,
blankLength,
blankWidth,
};
dispatch(updateOrder(sessionId, payload));
}
};
that gave me access to this state value, which i could then use in the onclick when the radio button was pressed. if the id was clearwater, the bool would be set to true, else it was set to false (only for the other two options because this code is used for other IDs)
onClick(id, ordAttribute) {
const { updateOrder, sessionId, validator } = this.props;
updateOrder(sessionId, { [ordAttribute]: id });
if (validator) validator(ordAttribute);
if (id === 'clearwater') {
updateOrder(sessionId, { clearwater: true });
} else if (id === 'kraft' || id === 'std_white_two_side'){
updateOrder(sessionId, { clearwater: false });
}
}
then all i needed to do was add this to the Service. if it was not printed !printed or not clearwater !clearwater, the checkbox would be disabled
const printing = {
doubleSided: {
display: 'Two Sided Print',
key: 'doubleSided',
component: 'checkbox',
disabled: state => !state.printed,
},
printed: {
display: 'Printed?',
key: 'printed',
component: 'checkbox',
disabled: () => false,
},
gloss: {
display: 'Gloss Finish',
key: 'gloss',
component: 'checkbox',
disabled: state => !state.printed || !state.clearwater,
},
};
I have a working example answering a similar problem, please, have a look. The whole logic is done in redux:
Want to uncheck the node of the tree structure in React JS

Changing a nested array value based on a given index in a reducer while not mutating state

I have a reducer working with an object with arrays. I want to change a value on the nested arrays based on a given index. This code works but I can't seem to get my test to work using deep freeze. I was trying to look at the redux example here http://redux.js.org/docs/basics/Reducers.html using .map to find the index with no luck. Any ideas?
export default function myReducer(state = { toDisplay: [] }, action) {
const { type, groupIndex, itemIndex } = action;
const newObject = Object.assign({}, state);
switch (type) {
case actionTypes.TOGGLE_GROUP:
newObject.toDisplay[groupIndex].isSelected = newObject.toDisplay[groupIndex].isSelected ? false : 'selected';
return newObject;
case actionTypes.TOGGLE_ITEM:
newObject.toDisplay[groupIndex].values[itemIndex].isSelected = newObject.toDisplay[groupIndex].values[itemIndex].isSelected ? false : true;
return newObject;
default:
return state;
}
}
EDIT:
For anyone curious after watching a helpful redux video I came up with this:
export default function myReducer(state = { toDisplay: [] }, action) {
const { type, groupIndex, itemIndex } = action;
switch (type) {
case actionTypes.TOGGLE_GROUP:
return {
...state,
toDisplay: [
...state.toDisplay.slice(0, groupIndex),
{
...state.toDisplay[groupIndex],
isSelected: state.toDisplay[groupIndex].isSelected ? false : 'selected'
},
...state.toDisplay.slice(groupIndex + 1)
]
};
case actionTypes.TOGGLE_ITEM:
return {
...state,
toDisplay: [
...state.toDisplay.slice(0, groupIndex),
{
...state.toDisplay[groupIndex],
values: [
...state.toDisplay[groupIndex].values.slice(0, itemIndex),
{
...state.toDisplay[groupIndex].values[itemIndex],
isSelected: state.toDisplay[groupIndex].values[itemIndex].isSelected ? false : true
},
...state.toDisplay[groupIndex].values.slice(itemIndex + 1)
]
},
...state.toDisplay.slice(groupIndex + 1)
]
};
default:
return state;
}
}
Using a helper/library like suggested is probably the best route but my team wishes to not add another dependency.
Firstly, Object.assign(...) only does a shallow copy. See here.
As you have objects nested inside arrays nested inside objects I would highly recommend the immutability helpers from react (as mentioned by Rafael). These allow you to do something like this:
case actionTypes.TOGGLE_GROUP:
return update(state, {
toDisplay: {
[groupIndex]: {
isSelected: {$set: newObject.toDisplay[groupIndex].isSelected ? false : 'selected'}
}
}
});
If you are looking to modify a simple value inside an array with raw js then you can use something like this:
return list
.slice(0,index)
.concat([list[index] + 1])
.concat(list.slice(index + 1));
(source)

Categories

Resources