Double spread problem - getting rid of Object.assign() - javascript

Here is my reducer body code fragment:
const newState = {
...state,
programs: {
...state.programs,
...Object.assign(
{},
...action.payload.map(
(channelName) =>
({
[channelName]: {
...state.programs[channelName],
selected: true
}
})
)
)
}
}
return newState
Is there any chance to get rid of Object.assign in this case?
The classical advice to change Object.assign({}, a) to { ...a } does not work in this case, because here we already have ...action.payload.map, so it would result in ... { ...a } which makes spread to produce array-like keys of 0,1,2...
Is there any elegant way to transform my code correctly?

Ever heard of reduce?
const action = {
payload: ['discoveryChannel']
}
const state = {
programs: {
cartoonNetwork: {
description: '',
when: new Date()
},
discoveryChannel: {
description: '',
when: new Date()
}
}
}
const newState = {
...state,
programs: {
...state.programs,
...action.payload.reduce(
(acc, channelName) => {
acc[channelName] = {
...state.programs[channelName],
selected: true
}
return acc;
}, {})
}
}
console.log(newState);

Another option to use Object.fromEntries:
const action = {
payload: ['discoveryChannel']
}
const state = {
programs: {
cartoonNetwork: {
description: '',
when: new Date()
},
discoveryChannel: {
description: '',
when: new Date()
}
}
}
const newState = {
...state,
programs: {
...state.programs,
...Object.fromEntries(
action.payload.map(
channelName => ([
channelName, {...state.programs[channelName], selected: true}
])
)
)
}
}
console.log(newState);

Related

react issue - Cannot assign to read only property '0' of object '[object Array]' ---

What causes the ff issue ? Cannot assign to read only property '0' of object '[object Array]' ?
Any idea would be appreacited. Thanks.
#ts code snippet
const [RegionalList, setRegionalList] = useState<IRegionalList[]>(RegionalListData);
const setEmailValue = (event: any, regionalId: number, index: number) => {
setRegionalList((prevState: IRegionalList[]) => {
const newState = prevState.map((prop: IRegionalList) => {
if (prop.id === regionalId) {
prop.emails[index] = { emailAddress: event.target.value, id: null };
return { ...prop };
}
return prop;
});
return newState;
});
}
here is the way that I suggest :
const [RegionalList, setRegionalList] = useState<IRegionalList[]>(RegionalListData);
const setEmailValue = (event: any, regionalId: number, index: number) => {
setRegionalList((prevState: IRegionalList[]) => {
const newState = prevState.map((prop: IRegionalList) => {
if (prop.id === regionalId) {
return { ...prop,emails:[...prop.emails.filter( (_,i)=>i !== index ),{ emailAddress: event.target.value, id: null }] }
}
return prop;
});
return newState;
});
}
and if you care about order of your list you can do this :
const setEmailValue = (event: any, regionalId: number, index: number) => {
setRegionalList((prevState: IRegionalList[]) => {
const newState = prevState.map((prop: IRegionalList) => {
if (prop.id === regionalId) {
let emails = prop.emails;
emails[index] = { emailAddress: event.target.value, id: null };
return { ...prop,emails }
}
return prop;
});
return newState;
});
}
please let me know if it fixed your problem

Angular & Firebase get data in observable in value changes

Problem with got data correctly execute function many one times, these function is execute in ngOnInit one time with abstraction but i dont know ocurrs these problem in a server, i thing in snapshotChanges but i don't know.
thx for help
https://i.stack.imgur.com/EinQg.png
return <Observable<Products[]>> t.db.collection(PATHS_FIRESTORE.products).snapshotChanges()
.pipe(
map(actions => {
let arr = actions.map((res) => {
let doc: any = <any>res.payload.doc.data()
let obj: any = {}
if (!isNullOrUndefined(cart)) {
for (const prod in cart) {
if (cart.hasOwnProperty(prod)) {
const element = cart[prod];
if (doc.uid === prod) {
obj[doc.uid] = {
name_product: doc.name_product,
path_img: doc.path_img,
price: doc.price,
quantity: doc.quantity + element.total,
uid: doc.uid,
uid_local: doc.uid_local
}
} else {
t.db.collection(PATHS_FIRESTORE.products).doc(prod).ref.get().then( res => {
const data = res.data()
return obj[res.id] = {
name_product: data.name_product,
path_img: data.path_img,
price: data.price,
quantity: element.total,
uid: doc.uid,
uid_local: doc.uid_local
}
})
}
}
console.log(obj)
}
return obj
}else {
obj = {
...doc
}
return obj
}
})
.filter((b: any) => {
return b.uid_local === uid_local
})
.filter((b: any) => {
return b.quantity > 0
})
.filter((b: any) => {
return !b.status
})
console.log(arr)
return arr
})
)

Function work incorrectly when using promise in node js

I have routes like that:
router.get('/:projectid/, (req, res) => {
testCase.getTestCaseDetail(req.params.projectid, req.params.testcaseid, req.params.snapshotId).then(testcaseData => {
res.render('testCaseService', {
title: 'Page',
testcase: testcaseData,
layout: 'project_layout',
});
});
});
In the handler function, I have getTestCaseDetail function:
function getTestCaseDetail(projectId, id, snapshotId) {
let testCaseId = parseInt(id);
return new Promise(((resolve, reject) => {
return testCaseSchema.aggregate([
{ $match: { 'projectId': projectId, 'testCaseId': testCaseId } },
{
$lookup: {
from: snapshotInfoSchema.collection.collectionName,
localField: testCaseObj.SERVICE_ID,
foreignField: 'artifacts.id',
as: 'services',
},
},
{ $unwind: '$services' },
{
$match: {
'services.snapshot.id': snapshotId,
}
}
]).then(testCaseResult => {
resolve(addTestCasesV2(testCaseResult, snapshotId));
})
.catch(err => {
reject(err);
})
}));
}
and addTestCasesV2 function
const addTestCasesV2 = function (testcases, snapshotId) {
const result = [];
let serviceTypeMapping;
let serviceName;
let testCase = {
id: '',
testCaseId: '',
name: '',
serviceName: '',
serviceType: '',
modifiedAt: '',
testScripts: '',
snapshotId: '',
services: '',
inputs: [],
outputs: [],
};
let promiseInputResults, promiseOutputResults;
const testcasesList = lodash.map(testcases, (tc) => {
const artifacts = lodash.map(tc.services.artifacts, (art) => {
if (art.id === tc.service_id) {
serviceTypeMapping = art.processType.serviceTypeName;
serviceName = art.name;
if (!commonUtil.isUndefined(art.processParameters)) {
if (!commonUtil.isUndefined(art.processParameters.input)) {
promiseInputResults = lodash.map(art.processParameters.input, (ip) => {
let classId = commonUtil.getArtifactId(ip.classId);
return objectType.getObjectTypeByClassId(snapshotId, classId)
});
}
if (!commonUtil.isUndefined(art.processParameters.output)) {
promiseOutputResults = lodash.map(art.processParameters.output, (ip) => {
let classId = commonUtil.getArtifactId(ip.classId);
return objectType.getObjectTypeByClassId(snapshotId, classId)
});
}
}
testCase.id = tc.testCaseId;
testCase.testCaseId = tc.testCaseId;
testCase.name = tc.name;
testCase.serviceName = serviceName;
testCase.serviceType = serviceTypeMapping;
testCase.modifiedAt = tc.modifiedAt;
testCase.testScripts = tc.testScripts;
testCase.snapshotId = snapshotId;
testCase.services = tc.services;
Promise.all(promiseInputResults).then(inputItems => {
return testCase.inputs = inputItems;
});
Promise.all(promiseOutputResults).then(outputItems => {
return testCase.outputs = outputItems;
});
}
});
});
return testCase;
};
The inputs/outputs is an list of item, like that:
inputs:[
{
name: "test1",
type: "String"
},
{
name: "test2",
type: "number"
},
]
I have a problem with promise lifecycle, this is the current flow
1. Routes
2. function getTestCaseDetail
3. resolve(addTestCasesV2(testCaseResult, snapshotId));
4. addTestCasesV2 ==> return testCase but without go to 2 promise.all functions
5. resolve(addTestCasesV2(testCaseResult, snapshotId));
6. Routes
7. go back 2 promise.all functions
8. end at return testCase.outputs = outputItems;
Please see the image to more detail flow (the white number is current flow, the orange number is my expect flow)
Please advice me. Many thanks.
Your code doesn't seem correct. If testcases is an array with more than one item, your lodash.map callback will be called testcases.length time. Each time overwriting testCase.id assigned in previous callback.
Anyways, I have corrected bits of your code to make it in run order that you wanted. I have logged ==step== at various places for your help.
First Function:
function getTestCaseDetail(projectId, id, snapshotId) {
let testCaseId = parseInt(id);
return new Promise(((resolve, reject) => {
return testCaseSchema.aggregate([
{ $match: { 'projectId': projectId, 'testCaseId': testCaseId } },
{
$lookup: {
from: snapshotInfoSchema.collection.collectionName,
localField: testCaseObj.SERVICE_ID,
foreignField: 'artifacts.id',
as: 'services',
},
},
{ $unwind: '$services' },
{
$match: {
'services.snapshot.id': snapshotId,
}
}
]).then(testCaseResult => {
console.log('=======STEP 1=======');
resolve(addTestCasesV2(testCaseResult, snapshotId));//=======STEP 2=======
console.log('=======STEP 5=======')
})
.catch(err => {
reject(err);
})
}));
}
Second function
const addTestCasesV2 = function (testcases, snapshotId) {
console.log('=======STEP 2=======')
const result = [];
let serviceTypeMapping;
let serviceName;
let testCase = {
id: '',
testCaseId: '',
name: '',
serviceName: '',
serviceType: '',
modifiedAt: '',
testScripts: '',
snapshotId: '',
services: '',
inputs: [],
outputs: [],
};
let promiseInputResults, promiseOutputResults;
return Promise.resolve()
.then(()=>{
console.log('=======STEP 3=======');
const testcasesList = lodash.map(testcases, (tc) => {
const artifacts = lodash.map(tc.services.artifacts, (art) => {
if (art.id === tc.service_id) {
serviceTypeMapping = art.processType.serviceTypeName;
serviceName = art.name;
if (!commonUtil.isUndefined(art.processParameters)) {
if (!commonUtil.isUndefined(art.processParameters.input)) {
promiseInputResults = lodash.map(art.processParameters.input, (ip) => {
let classId = commonUtil.getArtifactId(ip.classId);
return objectType.getObjectTypeByClassId(snapshotId, classId)
});
}
if (!commonUtil.isUndefined(art.processParameters.output)) {
promiseOutputResults = lodash.map(art.processParameters.output, (ip) => {
let classId = commonUtil.getArtifactId(ip.classId);
return objectType.getObjectTypeByClassId(snapshotId, classId)
});
}
}
testCase.id = tc.testCaseId;
testCase.testCaseId = tc.testCaseId;
testCase.name = tc.name;
testCase.serviceName = serviceName;
testCase.serviceType = serviceTypeMapping;
testCase.modifiedAt = tc.modifiedAt;
testCase.testScripts = tc.testScripts;
testCase.snapshotId = snapshotId;
testCase.services = tc.services;
/*=======FOLLOWING IS NOT REQUIRED=======*/
// Promise.all([promiseOutputResults]).then(outputItems => {
// return testCase.outputs = outputItems;
// });
}
});
});
return Promise.all([promiseInputResults,promiseOutputResults]);
})
.then(inputItems => {//array of resolved values
console.log('=======STEP 4=======');
testCase.inputs = inputItems[0];
testCase.outputs = inputItems[1];
return testCase;
})
};
Now you can use following to extract testcase from first function:
getTestCaseDetail(myProjectId, id, mySnapshotId)
.then(testCase=>console.log(testCase))
JSfiddle for your understanding.

setState is not merging the values

I use the following code in react in order to update state. state should finally looks like this:
this.state.output = {
'abc':{
value: 10
},
'cde':{
value: 20
}
// new values are added and old kept (if not overwritten)
}
My handler:
handleChange = (data) => {
this.setState(prevState => {
return {
output: {
[data.id]: { ...data },
},
}
})
}
When the data is passed in to handleChage with a new data.id, output does not add the new key, instead completely replace all its content
this.state.output = {
'new':{
value: 2
},
}
I need instead keep the previous key also. What is wrong in my code?
Because you forgot to add the other property and their values, update the object like this:
handleChange = (data) => {
this.setState(prevState => {
return {
output: {
...prevState.output, // notice this
[data.id]: { ...data },
},
}
})
}
Or simply:
handleChange = (data) => {
this.setState(prevState => ({
output: {
...prevState.output,
[data.id]: { ...data },
},
})
)}
object spread syntax is recent spec. Here is the documentation for it: using-object-spread-operator
Below code uses Object.assign method instead :
handleChange = (data) => {
this.setState(prevState => ({
output: Object.assign({}, prevState, {
[data.id]: data
})
})
)}

How to add to a state array in react-redux

kind of stuck on this little tidbit. I'm pretty new to redux so I'm just trying to figure it out. I'm trying to seperate my players array which come in on the response based on the players position. It all works except that the state is just constantly changing to the most recent player in that position instead of adding to the array of players in that position. Here are my actions and reducer:
//action.js
export const FETCH_PLAYERS_SUCCESS = 'FETCH_PLAYERS_SUCCESS';
export const fetchPlayersSuccess = players => ({
type: FETCH_PLAYERS_SUCCESS,
players
});
export const SET_WR = 'SET_WR';
export const setWR = wr => ({
type: SET_WR,
wr
});
export const SET_QB = 'SET_QB';
export const setQB = qb => ({
type: SET_QB,
qb
});
export const SET_RB = 'SET_RB';
export const setRB = rb => ({
type: SET_RB,
rb
});
export const SET_TE = 'SET_TE';
export const setTE = te => ({
type: SET_TE,
te
});
export const fetchPlayers = () => {
return dispatch => {
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "http://api.fantasy.nfl.com/v1/players/stats?statType=seasonStats&season=2017&week=1&format=json"; // site that doesn’t send Access-Control-*
fetch(proxyurl + url)
.then(res => res.json())
.catch(error => {
console.error('Error:', error)
dispatch(fetchPlayersError(error))
})
.then(response => {
let thisPlayer = response.players
for(let i=0; i<thisPlayer.length; i++){
if(thisPlayer[i].position == 'WR'){
dispatch(setWR(thisPlayer[i]))
}
if(thisPlayer[i].position == 'QB'){
dispatch(setQB(thisPlayer[i]))
}
if(thisPlayer[i].position == 'RB'){
dispatch(setRB(thisPlayer[i]))
}
if(thisPlayer[i].position == 'TE'){
dispatch(setTE(thisPlayer[i]))
}
}
dispatch(fetchPlayersSuccess(response))
});
}}
my reducer:
//reducer.js
const initialState = {
players: [],
loading: false,
error: null,
wr: [],
qb: [],
rb: [],
te: []
};
export default (state = initialState, action) => {
if (action.type === FETCH_PLAYERS_REQUEST) {
return Object.assign({}, state, {
loading: true,
error: null
});
}
if (action.type === FETCH_PLAYERS_SUCCESS) {
//console.log(state, action);
return Object.assign({}, state, {
players: action.players,
loading: false
});
}
if (action.type === SET_QB) {
//console.log(state, action);
return Object.assign({}, state, {
qb: action.qb,
loading: false
});
}
if (action.type === SET_RB) {
//console.log(state, action);
return Object.assign({}, state, {
rb: action.rb,
loading: false
});
}
if (action.type === SET_WR) {
//console.log(state, action);
return Object.assign({}, state, {
wr: action.wr,
loading: false
});
}
if (action.type === SET_TE) {
//console.log(state, action);
return Object.assign({}, state, {
te: action.te,
loading: false
});
}
if (action.type === FETCH_PLAYERS_ERROR) {
return Object.assign({}, state, {
loading: false,
error: action.error
});
}
return state;
};
thanks for any help in advance.
I think you are overwriting the state object.
I don't think your code is adding to an array, just overwriting it.
Also, I would pass the array from the response to one dispatch function, instead of looping and calling serval dispatch functions or format the object before you call dispatch. I think that would be easier to debug and maintain. You only need to call dispatch once, unless you have a compelling reason to call it several times.
Use a switch statement for your reducer and update each array only if needed, otherwise just keep the array from the current state.
I see that you are using Object.assign. Instead, try just updating the arrays individually as needed and return a new object for each action.
I don’t know all of your requirements, but this may help:
export const loadPlayers = formattedRespObj => ({
type: 'FETCH_PLAYERS_SUCCESS',
players: formattedRespObj.players,
wr: formattedRespObj.wr,
qb: formattedRespObj.qb,
rb: formattedRespObj.rb,
te: formattedRespObj.te
});
function formatRespObj(playersResp) {
let formattedRespObj = {players: [], wr: [], qb: [], rb: [], te: []};
// Note, this can probably be prettier
for (let i = 0; i < playersResp.length; i++) {
switch (playersResp[i].position) {
case 'WR':
formattedRespObj.wr.push(playersResp[i]);
break;
case 'QB':
formattedRespObj.qb.push(playersResp[i]);
break;
case 'RB':
formattedRespObj.rb.push(playersResp[i]);
break;
case 'TE':
formattedRespObj.te.push(playersResp[i]);
break;
default:
console.error("Unknown player position");
break;
}
}
formattedRespObj.players = [...formattedRespObj.wr, ...formattedRespObj.qb, ...formattedRespObj.rb, ...formattedRespObj.te];
return formattedRespObj;
}
const initialState = {
players: [],
loading: false,
error: null,
wr: [],
qb: [],
rb: [],
te: []
};
export default function reducer(playersState = initialState, action) {
switch (action.type) {
case 'LOAD_PLAYERS':
return {
players: [...playersState.wr, playersState.wr],
loading: true,
error: null,
wr: [...playersState.wr, action.wr],
qb: [...playersState.qb, action.qb],
rb: [...playersState.rb, action.rb],
te: [...playersState.te, action.te],
};
case 'FETCH_PLAYERS_ERROR':
return {
loading: false,
error: action.error,
wr: playersState.wr,
qb: playersState.qb,
rb: playersState.rb,
te: playersState.te
};
default:
return {
loading: false,
error: null,
wr: playersState.wr,
qb: playersState.qb,
rb: playersState.rb,
te: playersState.te
}
}
}
export const fetchPlayers = () => {
return dispatch => {
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "http://api.fantasy.nfl.com/v1/players/stats?statType=seasonStats&season=2017&week=1&format=json"; // site that doesn’t send Access-Control-*
fetch(proxyurl + url)
.then(res => res.json())
.catch(error => {
console.error('Error:', error);
dispatch(fetchPlayersError(error));
})
.then(response => {
let formattedRespObj = formatRespObj(response);
dispatch(loadPlayers(formattedRespObj));
dispatch(fetchPlayersSuccess(response))
});
}
};

Categories