Reducer keep returning an empty array - javascript

I'm trying to return an array from the reducer after filling it with data, but it always returns an empty array.
export default function dashboards(state: any = dashboardsState, action: any) {
// more code
if (action.type === Types.LOAD_DASHBOARD_SUCCESS) {
// create the cards holder in the store
var cardsSkull: any = [];
Object.keys(action.payload.lists).map((idLis: any) => {
cardsSkull[idLis] = {};
console.log("still up");
});
action.payload.importedDashboards.map((impDash: any) => {
Object.keys(impDash.mappedLists).map((key: any) => {
const mpList = impDash.mappedLists[key];
cardsSkull[mpList.idlistLocal][impDash.remote_board_id] = [1, 2, 3];
console.log("still working");
});
});
console.log(cardsSkull);
// assign and ready to go
return Object.assign({}, state, {
selectedDashboardData: action.payload,
cards: cardsSkull
});
}
// more code
return state;
}
when I console.log(cardsSkull) everything looks right.
I expect the cards to have the value of cardsSkull after it is filled, but the actual output is an empty array

You define cardsSkull as array (which is an object under the hood), but because idLis is not a number, cardsSkull[idLis] = {}; does not populate an element within the array part of the object, but rather an element within the array object itself. Therefore in your screenshot, the length property of your array is still 0! Use numbers for idLis and your problem is solved.

it seems that you are assuming wrong type for cards, it is an object instead of array. I would suggest to update as following
export default function dashboards(state: any = dashboardsState, action: any) {
// more code
if (action.type === Types.LOAD_DASHBOARD_SUCCESS) {
// create the cards holder in the store
const cardsSkull = Object.keys(action.payload.lists).reduce(
(acc, idLis) => ({ ...acc, [idList]: {} }),
{}
);
action.payload.importedDashboards.forEach((impDash: any) => {
Object.keys(impDash.mappedLists).forEach((key: any) => {
const mpList = impDash.mappedLists[key];
cardsSkull[mpList.idlistLocal][impDash.remote_board_id] = [1, 2, 3];
console.log('still working');
});
});
console.log(cardsSkull);
// assign and ready to go
return {
...state,
selectedDashboardData: action.payload,
cards: cardsSkull
}
}
// more code
return state;
}

Related

.map not is not a function on empty object?

I am new to react. I have set up a reducer with the state as an empty object. But when using the .map() function it doesn't seem to work on the object? Does the .map only work on arrays?
export const orders = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case "NEW_ORDER":
const { new_order } = payload;
const new_state = { ...state, new_order };
console.log(new_state);
return new_state;
}
return state;
}
Well, no, you can not use .map() on an object since it is supposed to be used in an array. By the way, if you are trying to store a list of orders, you should use am array instead, so initialize your state with [] and not with {}, or with a key that actually contains your orders like { orders: [] } and then add the order you received like const new_state = { ...state, orders: [...state.orders, new_order] }.
You are correct. The map function is part of the Array prototype.
See here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
However, I don't see a map function in the code sample you posted?
If you want to loop over an object though you can use Object.entries.
const myObj = {foo: "bar"}
const result = Object.entries(myObj).map(([key, value]) => {
console.log(key) // "foo"
console.log(value) // "bar"
return `${key}-${value}`
})
console.log(result) // ["foo-bar"]

Creating new array vs modifing the same array in react

Following is the piece of code which is working fine, but I have one doubt regarding - const _detail = detail; code inside a map method. Here you can see that I am iterating over an array and modifying the object and then setting it to setState().
Code Block -
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
const { invoiceData } = this.state;
invoiceData.map(invoiceItem => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(detail => {
const _detail = detail;
if (_detail.tagNumber === data.tagNumber) {
_detail.id = data.id;
}
return _detail;
});
}
return invoiceItem;
});
state.invoiceData = invoiceData;
}
this.setState(state);
};
Is this approach ok in React world or I should do something like -
const modifiedInvoiceData = invoiceData.map(invoiceItem => {
......
code
......
})
this.setState({invoiceData: modifiedInvoiceData});
What is the pros and cons of each and which scenario do I need to keep in mind while taking either of one approach ?
You cannot mutate state, instead you can do something like this:
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
this.setState({
invoiceData: this.state.invoiceData.map(
(invoiceItem) => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(
(detail) =>
detail.tagNumber === data.tagNumber
? { ...detail, id: data.id } //copy detail and set id on copy
: detail //no change, return detail
);
}
return invoiceItem;
}
),
});
}
};
Perhaps try something like this:
checkInvoiceData = (isUploaded, data) => {
// Return early
if (!isUploaded) return
const { invoiceData } = this.state;
const updatedInvoices = invoiceData.map(invoiceItem => {
if (invoiceItem.number !== data.savedNumber) return invoiceItem
const details = invoiceItem.details.map(detail => {
if (detail.tagNumber !== data.tagNumber) return detail
return { ...detail, id: data.id };
});
return { ...invoiceItem, details };
});
this.setState({ invoiceData: updatedInvoices });
};
First, I would suggest returning early rather than nesting conditionals.
Second, make sure you're not mutating state directly (eg no this.state = state).
Third, pass the part of state you want to mutate, not the whole state object, to setState.
Fourth, return a new instance of the object so the object reference updates so React can detect the change of values.
I'm not saying this is the best way to do what you want, but it should point you in a better direction.

Which approach in React is better?

Below both code does exactly same but in different way. There is an onChange event listener on an input component. In first approach I am shallow cloning the items from state then doing changes over it and once changes are done I am updating the items with clonedItems with changed property.
In second approach I didn't cloned and simply did changes on state items and then updated the state accordingly. Since directly (without setState) changing property of state doesn't call updating lifecycles in react, I feel second way is better as I am saving some overhead on cloning.
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const clonedItems = Array.from(items);
clonedItems.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items: clonedItems });
};
OR
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items });
};
You can use this
this.setState(state => {
const list = state.list.map(item => item + 1);
return {
list,
};
});
if you need more info about using arrays on states, please read this: How to manage React State with Arrays
Modifying the input is generally a bad practice, however cloning in the first example is a bit of an overkill. You don't really need to clone the array to achieve immutability, how about something like that:
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const processedItems = items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
return {
...ele,
rate: Number(value)
};
} else {
return ele;
}
});
this.setState({ items: processedItems });
};
It can be refactored of course, I left it like this to better illustrate the idea. Which is, instead of cloning the items before mapping, or modifying its content, you can return a new object from the map's callback and assign the result to a new variable.

tranforming RxJS Observable

I use angularFirestore to query on firebase and I want join data from multiple documents using the DocumentReference.
The first operator map in the pipe return an array of IOrderModelTable, the second operator, i.e, the switchMap iterate over array an for each element use the id contained in each element to query data in other table.
The problem is that in the swithMap I obtain an array of observable due to anidated map operators. How I can obtain an array of IOrderModelTable and then return an observable of this array.
The code is:
getDataTableOperatorsFromDB(): Observable<IOrderModelTable[]> {
const observable = this.tableOperatorsCollectionsRef.snapshotChanges().pipe(
map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as IOrdersModelDatabase;
const id = a.payload.doc.id;
data.ot = id;
return data;
});
}),
switchMap(data => {
const result = data.map(element => {
return this.afs.collection('Orders/').doc(element.orderNumberReference.id).valueChanges().pipe(map(order => {
return {
otNumber: element.ot,
clientName: '',
clientReference: order.clientReference,
id: element.orderNumberReference,
};
}));
});
// Result must be an IOrderModelTable[] but is a Observable<IOrderModelTable>[]
return of(result);
})
);
You can use to Array operator to transform a stream to an array, but make sure your stream will end.
The trick is to choose the right stream.
For you problem, the natural source would be the list received by your first call. In a schematic way I can put it , you get a list of ids, that you transform into a list of augmented information :
first input ...snapshopChanges():
----[A, B, C]------>
each element is transformed through ...valueChanges():
-------Call A -------------DataA-------->
-------Call B ------------------------DataB----->
-------Call C --------------------DataC----->
Then reduced using toArray() to :
----------------------------------------------[DataA, DataC, DataB]-------->
Code:
getDataTableOperatorsFromDB(): Observable<IOrderModelTable[]> { {
return this.tableOperatorsCollectionsRef.snapshotChanges()
.pipe(
map(actions => {
from(data).pipe(
map(action => {
const data = a.payload.doc.data() as IOrdersModelDatabase;
const id = a.payload.doc.id;
data.ot = id;
return data;
}),
mergeMap(element => {
return this.afs.collection('Orders/').doc(element.orderNumberReference.id).valueChanges().pipe(
map(order => {
return {
otNumber: element.ot,
clientName: '',
clientReference: order.clientReference,
id: element.orderNumberReference,
};
})
);
}),
toArray()
);
})
)
}
Important : I replaced switchMap by mergeMap, otherwise some information could be thrown away.
#madjaoue
You're right, mergeMap is the correct operator in this case because with switchMap for each event emitted the inner observable is destroyed so in the subscribe you only get the final event emitted, i.e, the last row. This observable is long lived, never complete, so also use the operator take with the length of the actions which is the array that contains the list of documents.
Thank you very much for the help. :D
getDataTableOperatorsFromDB(): Observable<IOrderModelTable[]> {
const observable = this.tableOperatorsCollectionsRef.snapshotChanges().pipe(
switchMap(actions => {
return from(actions).pipe(
mergeMap(action => {
console.log(action);
const data = action.payload.doc.data() as IOrdersModelDatabase;
const otNumber = action.payload.doc.id;
return this.afs.collection('Orders/').doc(data.orderNumberReference.id).valueChanges().pipe(map(order => {
return {
otNumber: otNumber,
clientName: '',
clientReference: order.clientReference,
id: data.orderNumberReference,
};
}));
}),
mergeMap(order => {
console.log(order);
return this.afs.collection('Clients/').doc(order.clientReference.id).valueChanges().pipe(map(client => {
return {
otNumber: order.otNumber,
clientName: client.name,
clientReference: order.clientReference,
id: order.id,
};
}));
}),
take(actions.length),
toArray(),
tap(console.log),
);
}),

Reducer cannot read property 'photos' of undefined? What am I doing wrong?

Here is the initial state of my reducer, and I need to set it up in this way due to some post processing I need to do:
const initialState = {
showAll: {
photos: null
}
}
Basically, I have a page where you see all your photos, and you can tag certain ones as your pinned photos.
Here's part of my reducer logic:
if (state.showAll.photos) {
const showAllState = state.showAll.photos;
showAllState.map(m => {
if (action.payload.id === m.id) {
m.pinned = true;
}
});
showAllAfterPin = showAllState;
} else {
showAllAfterPin = state.showAll.photos;
}
However, I get an error saying cannot read property 'photos' of undefined and I'm not sure what I am doing wrong.
Might be easier to just set your photos in initialState to empty array [] instead of null.
Another thing, your reducer should not mutate your state object.
Doing const showAllState = state.showAll.photos doesn't make it a new object.
Last thing, showAllState.map(...) needs to return an item inside the function body. It will create a new array.
Here's something you can do...
const { photos = [] } = state.showAll;
const updatedPhotos = photos.map(photo => {
if (action.payload.id === photo.id) {
return Object.assign({}, photo, { pinned: true })
}
return photo;
});
// return entire state if this is inside your root reducer
return {
...state,
showAll {
...state.showAll,
photos: updatedPhotos
}
}

Categories

Resources