immutable helper updating value in object in array - reactjs

I'm trying to understand immutable helper in the context of what I'm trying to do.
I'm trying to update the objects inside based on when an onChange event calls the INPUT_CHANGE action. it should add to the formData value not replace it. I tried $add but that didn't work the way I wanted it either.
Also every time a a new input filed is changed it adds a new object with the same updated changes.
Array[index] -> input1 -> INPUT_CHANGE -> update object with key and value of input field as it changes.
inputs=[{key:{key:'input name', value: mew}}]
Array[index] -> input2 -> INPUT_CHANGE -> update object with key and value of input field as it changes.
inputs=[{key:{key:'input name', value: mew}}, {key:{key:'input name', value: mew}}]
and so on ...
const setupState = {
count: 0,
inputs: [{}],
};
export default (state = setupState, action) => {
switch (action.type) {
case INPUT_CHANGE: {
// const formDataArr = state.inputs[count];
return update(state, {
inputs: [{
key: { $set: action.key },
value: { $set: action.value },
}],
});
}
default: return state;
}
};
Kind of confused as to how to use immutable helper for this ?
UPDATE:
This is how the the object should look as user updates each input.
inputs: [
{
"score": {
"key": "score",
"value": "20....",
},
"hits": {
"key": "hits",
"value": "ss..",
}
}
]

Try this.
export default (state = setupState, action) => {
switch (action.type) {
case INPUT_CHANGE: {
// const formDataArr = state.inputs[count];
return update(state, {
inputs: [
// contain origin state.inputs!!!!!!!!!!!!!!!!!!!
...state.inputs,
{
key: { $set: action.key },
value: { $set: action.value },
}],
});
}
default: return state;
}

Related

add property to object in state array with useReducer

I have an array of objects stored in state that looks like:
{
"file": {},
"myFsize": 1321,
"fileType": "image/jpeg"
},
{
"file": {},
"myFsize": 999,
"fileType": "image/jpeg"
}
]
I am using useHookReducer to add and remove objects successfully
const [fileSelectedArrax, setfileSelectedArrax] = useReducer(fileSelectReducer, [])
however I want to also add an upload progress to each object as it uploads. It shhould look like this...
{
"file": {},
"myFsize": 1321,
"fileType": "image/jpeg",
"percent": "90"
},
{
"file": {},
"myFsize": 999,
"fileType": "image/jpeg",
"percent": "100"
}
]
This is my reducer:
function fileSelectReducer(state, action) {
switch (action.type) {
case 'add':
console.log(state)
return [...state, action.filex]
case 'adduploadprogress':
//WHAT HERE?
return [...state]
case 'remove':
const update = [...state]
update.splice(update.indexOf(action.file), 1)
document.getElementById(fileattach).value = ''
return update
default:
return state
}
}
My function to invoke this is:
function adduploadprogress({ file, percent }) {
const filex = { file, percent }
console.log(filex)
setfileSelectedArrax({ filex, type: 'adduploadprogress' })
}
I have tried for loops and ternerys to match the object in state to the file being passed in in the action, but nothing seems to work
You can map the array of files (the state), and replace the current file's object with a new object that contains the new percent:
case 'adduploadprogress':
return state.map(f => f.file === action.filex.file ? ({
...f,
percent: action.filex.percent,
}) : f)

State didn't change in Redux

I have this reducer that has in initial state and array of plans that has array of visits that has an array of treatments.
The idea is when action (Remove_treatment) is triggered, it removes one of the treatment as following:
import { REMOVE_TREATMENT } from "./treatmentPlanTypes";
const initialState = {
plans: [
{
id: 1,
visits: [
{
id: 1,
treatments: [
{
id: 1,
name: "treatment1",
},
{
id: 2,
name: "treatment2",
},
],
},
{
id: 2,
treatments: [
{
id: 1,
name: "treatment1",
},
{
id: 2,
name: "treatment2",
},
],
},
],
},
],
};
const treatmentPlanReducer = (state = initialState, action) => {
switch (action.type) {
case REMOVE_TREATMENT:
let clonePlans = [...state.plans];
const visitIndex = clonePlans[0].visits.findIndex(
(i) => i.visitId === action.visitId
);
const treatmentIndex = clonePlans[0].visits[
visitIndex
].treatments.findIndex((i) => i.treatmentId === action.treatmentId);
clonePlans[0].visits[visitIndex].treatments.splice(treatmentIndex, 1);
console.log(JSON.stringify(clonePlans));
return {
...state,
plans: [...clonePlans],
};
default:
return state;
}
};
When I console.log(clonePlans) I get the correct array.
But when I return the value like:
return {
...state,
plans: [...clonePlans],
};
I'm still having the old plan and not updated in the component.
If I tried to enter the expected plan as I get from console.log directly in the above return statement, the component is updated successfully.
this is a part of the component in my code:
const plans = useSelector((state) => state.treatmentPlan.plans, shallowEqual)
const listOfPlans = plans.map((plan) => (
<PlanArea key={plan.planId} visits={plan.visits} />
));
console.log(listOfPlans);
The component is still showing the old plan, but console.log(listOfPlans) shows the correct array.
You need to deeply copy your plans array for your component to feel the change in state, and hence re-render
let clonePlans = JSON.parse(JSON.stringify(state.plans))
return { ...state, plans: clonePlans };

Replace item in array with new value using dispatch in React

I've got an initial array, which can be added to and deleted from, no problems there..
const initialItems = [
{
id: Date.now(),
text: 'Get milk',
},
{
id: Date.now(),
text: 'Get eggs',
},
]
..but I'm trying to figure out how to edit the text effectively of one of the items using a dispatch function.
My dispatch looks like this:
const editItemHandler = () => {
dispatch({
type: 'EDIT_ITEM',
id: Date.now(),
text: itemInputRef.current.value,
index,
})
}
Which is just passing the value of an input
<input
autoFocus
type='text'
ref={itemInputRef}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setToggle(!toggle)
}
if (e.key === 'Enter') {
// Dispatch
editItemHandler()
setToggle(!toggle)
}
}}
/>
My reducer file looks like this:
const itemReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM': {
return [
...state,
{
id: action.id,
text: action.text,
},
]
}
case 'EDIT_ITEM': {
// Attempt 1
return [...state.splice((item, index) => index, 1, action.text)]
// Attempt 2
return [
...state.filter((item, index) => index !== action.index),
{
id: action.id,
text: action.text,
},
]
}
case 'DELETE_ITEM': {
return [...state.filter((item, index) => index !== action.index)]
}
default: {
return state
}
}
}
export default itemReducer
I've commented in 2 approaches I've already tried in the 'EDIT_ITEM' type.
Approach 1 just deletes the item and adds a new valued one albeit at the bottom of the array, which isn't what I want so I'd have to try and reorder after.
Approach 2 is using splice, which I thought was what would work for replacing an item with the specified value. However all it returns is ONLY the 'edited' with the original text (so not even edited), and deletes everything else.
How am I using this function incorrectly, or is there a better approach to editing an item in place? I'm obviously doing something wrong but can't figure out what. I've searched about and tried various approach to no avail.
Ideally I'd want the item to also keep the same id as before as well, so how to keep that would be a plus.
To update an item in an array you have several choices :
case 'EDIT_ITEM': {
// using map
return state.map((item, i) =>
i === action.index ? { id: action.id, text: action.text } : item
// using slice
return [
...state.slice(0, action.index),
{ id: action.id, text: action.text },
...state.slice(action.index+1)
]
This is an incorrect use of splice
return [...state.splice((item, index) => index, 1, action.text)]
because splice return an array containing the deleted elements, and it doesn't accept an function as first argument but the index at which to start changing the array.
the correct way with splice :
case 'EDIT_ITEM': {
// using splice
let newState = [ ...state ]
newState.splice(action.index, 1, { id: action.id, text: action.text })
// or you can directly do
newState[action.index] = { id: action.id, text: action.text }
// and return the new state
return newState;

Redux - Adding element to array nested in an object inside an array

Can't figure out how to properly insert an element into an array, that's inside an object, that's inside an array. Here's an example of my default data for structure:
const defaultState = {
myinbox: [
{
owner: 'John Lennon',
owner_id: 1,
read: true,
messages: [
{
_id: 1,
text: 'When will you be home?',
createdAt: new Date(Date.UTC(2017, 10, 11, 11, 20, 0)),
user: {
_id: 1,
name: 'John Lennon'
}
}
...
I want to add another message when an inbound message comes in. This is what the snippet from my reducer looks like:
const inboxReducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD_INBOUND_MESSAGE':
return {
...state,
myinbox: [
state.myinbox[action.payload.index]: {
...state.myinbox[action.payload.index],
messages: [
...state.myinbox[action.payload.index].messages,
action.payload.msg
]
}
...state.myinbox,
]
}
default:
return state
}
}
The index of the parent "owner" is passed as the index inside the payload, and the new message is msg in the payload.
I can't figure out how to write this reducer without mutating the original state.
You're mutating the original state when you set myinbox using state.myinbox[action.payload.index]:.
Looks like you're trying to set the state for the index using computed property keys. The syntax for that would be:
myinbox: [
[action.payload.index]: {
...state.myinbox[action.payload.index],
messages: [
...state.myinbox[action.payload.index].messages,
action.payload.msg
]
}
...state.myinbox,
]
This can be done with Immer
const { index, msg } = action.payload;
return produce(state, (draftState) => {
draftState.myinbox[index].messages.push(msg);
});

Updating state with nested array of objects

This is something also called as deep state update. Where I have to update nested state array.
I am implementing a simple redux application. Here I want to update the state which is nested array of object. My reducer function takes state, action. I have to update responses property of state with new value. I tried to map/iterate the state but it isnt working for me. Is there a way to update those specific values and return update state.
const sampleData = [{
"id": 1,
"text": "Hobby",
"answers": [{
"id": 1,
"text": "Chess",
"responses": 5
}]
}];
const action = {
type: "VOTE",
questionId: 1,
answerId: 3
};
This is handleSubmit function I am calling when Submit button is clicked on form.
handleSubmit(e){
const store = createStore(hobbyReducer, hobby); // created store here
var k = (document.querySelector('input[name = "hobby"]:checked').value);
store.dispatch({type:"SUBMIT", text: k, id: 1}); // Dispatching action to reducer
store.subscribe(()=>{
console.log(store.getState());
});
}
Here is reducer part of program:
function hobbyReducer(state, action) {
switch(action.type){
case "SUBMIT":
return{
...state,
answers: state.answers.map(e=> (e.text === action.text && e.answers.id === action.id)?
{ ...answers,
responses: (e.responses+1)} :
hobby )
}
break;
default:
return state;
}
}
initial state = sampleData; // Array object given above
I am unable to update the responses property which is in a nested array
This is the code I wanted to write, after some research I finally did what was required. Although this is not solution in terms of time complexity.
`
case "SUBMIT":
const updatedState = state.map(e =>{
if(e.id === action.id)
return{...e,
answers: e.answers.map(f =>{
if(f.text === action.text){
return{
...f,
...{responses: f.responses + 1},
}
}
return f;
})
}
return e;
})
console.log("updatedstate", updatedState)
return updatedState
Just an error in your map I think:
function hobbyReducer(state, action) {
switch(action.type) {
case "SUBMIT":
return {
...state,
answers: state.answers.map(answer => {
if (answer.text === action.text && answer.id === action.id) {
answer.response = answer.response + 1;
}
return answer;
});
}
default:
return state;
}
}

Resources