Redux's Reducer - updating with keys as numbers - reactjs

In my current implementation, I have an initial state as so:
const state = {
1: {id: 1, status: false},
2: {id: 2, status: false}
}
I'm having a hard time how to implement the reducer correctly if I wanted to update the status on either the item.
I have the following as so:
export default function (state = initialState, action) {
switch (action.type) {
case UPDATE_TO_TRUE: {
const { itemID } = action;
let item = itemID.toString();
return {
...state,
item: {
status: true
}
}
}
default: {
return state;
}
}
}
Essentially I can't figure out how to pinpoint the right key. What's tricky is that the keys are setup as numbers.

Try using a computed property key in the object literal definition:
return {
...state,
[item]: {
id: item,
status: true
}
}

You can access the item you want using bracket notation, which works as long as the key can be represented by a string. So for example:
const state = {
1: {id: 1, status: false},
2: {id: 2, status: false}
};
console.log(state['2']);
returns your second item.

Related

Redux : Updating object values inside array immutably

const initialState = {
arr: [
{
name: "Chicken",
grade: "A",
quantity: 0
},
{
name: "Mutton",
grade: "B",
quantity: 0
},
{
name: "Sandwich",
grade: "A-Plus",
quantity: 0
}
]
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_QUANTITY:
return {
...state,
arr: {
...state.arr,
[state.arr[action.index]]: {
...state.arr[action.index],
[state.arr[action.index][0].quantity]:
[state.arr[action.index][0].quantity] + 1
}
}
};
default:
return state;
}
};
I'm trying to update the quantity immutably. Each time I click on a button, the quantity should be increased by 1. The above code which I have written is wrong (hence posting the query here). I'd appreciate it if anyone could show me where I went wrong and point me in the right direction.
The final output I am expecting is:
arr: [
{
name: "Chicken",
grade: "A",
quantity: 1 // Updated value
},
{
name: "Mutton",
grade: "B",
quantity: 0
},
{
name: "Sandwich",
grade: "A-Plus",
quantity: 0
}
]
There are a few things that won't work with your current code. Your initial state defines arr as an Array, but your reducer is returning an Object instead. Also, when you are trying to access the quantity key of the object inside arr, you are using an additional [0] index accessor that doesn't match with your data structure.
I would also suggest that you compose reducers (using combineReducers), to make it easier for you to keep track of the data structure. Combining reducers lets you handle individual levels of your data structure without having to worry about the whole structure. Also, using the spread operator works well for objects, but functions like map sometimes are clearer when manipulating arrays.
Something like this would do what you need:
const arr = (state = initialState.arr, action) => {
switch (action.type) {
case actionTypes.ADD_QUANTITY:
return state.map((element, i) => {
return i === action.index ? {...element, quantity: element.quantity + 1} : element;
});
default:
return state;
}
}
const rootReducer = combineReducers({arr});

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;
}
}

Overwriting array of objects instead of updating it

Im currently learning redux and trying few options out. Everything looks okay until I want to update one state in the array of objects.
I'm dispatching 5 actions in total now, first 2 setting longitutde and latitude for one part of the state using one reducer, then I set IDs with other reducer and finally when I want to update one of the objects in the array I update one but delete the other somehow.
My file looks like this:
const demoState = {
renderedDrugs: [
{
id: '',
drugId: '',
price: undefined
}
],
apt: {
latA: '',
lonA: ''
}
}
//SET_ID
const setId = (id, drugId) => ({
type: 'SET_ID',
renderedDrugs: {
id,
drugId
}
})
//SET_PRICE
const setPrice = (drugId, price) => ({
type: 'SET_PRICE',
drugId,
price
})
//RENDERED DRUGS REDUCER
const renderedDrugsReducerDefState = [];
const renderedDrugsReducer = (state = renderedDrugsReducerDefState, action) => {
switch (action.type) {
case 'SET_ID':
return [
...state,
action.renderedDrugs
]
case 'SET_PRICE':
return state.map((drug) => {
if (drug.drugId === action.drugId) {
return {
...drug,
...action.price
}
}
})
default:
return state;
}
}
//SET_LAT
const setLat = (latA) => ({
type: 'SET_LAT',
latA
})
//SET_LON
const setLon = (lonA) => ({
type: 'SET_LON',
lonA
})
//APT REDUER
const aptReducerDefState = []
const aptReducer = (state = aptReducerDefState, action) => {
switch (action.type) {
case 'SET_LAT':
return {
...state,
latA: action.latA
}
case 'SET_LON':
return {
...state,
lonA: action.lonA
}
default:
return state;
}
}
//STORE CREATION
const store = createStore(
combineReducers({
renderedDrugs: renderedDrugsReducer,
apt: aptReducer
})
)
store.subscribe(() => {
console.log('store', store.getState());
})
store.dispatch(setLat(88));
store.dispatch(setLon(78));
store.dispatch(setId(uuid(), 3));
store.dispatch(setId(uuid(), 35));
store.dispatch(setPrice(35, {price: 400}));
I assume the SET_PRICE action is at fault, but I tried various configurations and cant figure out the issue so thats why I posted the whole file, if thats unnecessary let me know and I will delete irrelevant bits.
Console log after 4th dispatch:
{renderedDrugs: Array(2), apt: {…}}
apt
:
{latA: 88, lonA: 78}
renderedDrugs
:
Array(2)
0
:
{id: "2a3c4bca-610a-4554-b7e3-695ae6e30ae7", drugId: 3}
1
:
{id: "48df057a-c8f1-4138-8db7-6268f7508ccb", drugId: 35}
length
:
2
__proto__
:
Array(0)
__proto__
:
Object
and aftr 5th
{renderedDrugs: Array(2), apt: {…}}
apt
:
{latA: 88, lonA: 78}
renderedDrugs
:
Array(2)
0
:
undefined
1
:
{id: "48df057a-c8f1-4138-8db7-6268f7508ccb", drugId: 35, price: 400}
length
:
2
__proto__
:
Array(0)
__proto__
:
Object
The .map doesn't return the unchanged objects for the items your're not updating. Adding a return should fix it:
return state.map((drug) => {
if (drug.drugId === action.drugId) {
return {
...drug,
...action.price
}
}
return drug; // this was missing before, so the return was undefined
})

redux - action to fill an array and then reset

I am trying to fill a small array [Should have two elements]. The initial state is
selected: []
Once it fills 2 slots [a, b], it should compare if the numbers are equal (return true or false) and reset the array to the original state [].
export const selectTile = (idx) => {
return {
type: SELECT_TILE,
idx
};
};
const initialState = {
selected: [],
matched: 0,
score: 0
};
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_TILE:
return { ...state, selected: state.selected.push(action.idx) };
default:
return state;
}
};
I have already stumbled upon the first problem. The above reducer returns an error 'state.selected.push is not a function'.
When the first action gets dispatched, it should do:
check if the array's length is smaller than 2
If yes, push the element to the array:
selected: [x]
another action:
check if the array's length is smaller than 2
yes, push the element to the array:
selected: [x, y]
another action:
check if the array's length is smaller than 2
no - compare if x === y, (return something, eg. true or false or any flag)
regardless of whether x===y or not, reset the 'selected' array to [] and wait for another action.
EDIT:
I think my description above was not accurate (btw, an action is dispatched on click of a tile):
action 1: [x]
action 2: [x, y] (if x=y, matched: true, selected: [])
action 3: [x] (the moment this action is dispatched, it should clear
the 'selected' array and a new element should be added to the array.
As it stands with the answer below, the third actions just clears the
array, but no new element is added.
I have adapted the answer below but it gives me an error that state.slice is not an action (error appears when I click on a tile for the second time (action 2 being dispatched).
case SELECT_TILE:
if (state.selected.length < 1) {
return { ...state, selected: [...state.selected, action.idx] };
} else if (state.selected.length === 1) {
const nextState = state.slice();
nextState.selected.concat(action.idx);
if (nextState.selected[0] === nextState.selected[1]) {
return { ...nextState, score: nextState.score + 1, selected: [] };
}
return { ...nextState, selected: [] };
}
You must use concat to push an item to the state array as it retun
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_TILE:
return { ...state, selected: state.selected.concat(action.idx) };
default:
return state;
}
};
or you can do it with the spread operator itself, which i think is a better way
return { ...state, selected: [...state.selected, action.idx] };
As per your requirement is considered
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_TILE:
if(state.selected.length < 2) {
return { ...state, selected: [...state.selected, action.idx] };
} else {
if(state.selected[0] === state.selected[1]) {
return {...state, matched: true, selected: []}
}
}
default:
return state;
}
};

Resources