spread operator (...) is creating extra fields in array in es6 - javascript

I wanted to embed a new key/value pair in the respective indexed array of objects based on an onChange event.
However, it is done correctly but adding extra elements in the array.
Original array of objects:
0:{data: {…}}
1:{data: {…}}
2:{data: {…}}
3:{data: {…}}
4:{data: {…}}
Achieved result:
0:{data: {…}}
1:{data: {…}}
2:{data: {…}, origin: "UK"}
3:{data: {…}, origin: "UK"}
4:{data: {…}}
5:"UK"
6:"UK"
Intended result:
0:{data: {…}}
1:{data: {…}}
2:{data: {…}, origin: "UK"}
3:{data: {…}, origin: "UK"}
4:{data: {…}}
Below is my code doing it in a loop:
render: (rowData, indexes) => {
return (
<SelectField
id={`origin-${indexes.rowIndex}`}
defaultValue="US"
style={{ position: 'absolute' }}
onChange={text => {
this.setState(
{
generalPermitSelectedVehicles: [
...generalPermitSelectedVehicles,
(generalPermitSelectedVehicles[
indexes.rowIndex
].origin = text),
],
},
() => {
console.log({
generalPermitSelectedVehicles: this.state
.generalPermitSelectedVehicles,
});
},
);
}}
menuItems={[
{
label: 'America',
value: 'US',
},
{
label: 'United Kingdom',
value: 'UK',
},
{
label: 'Oman',
value: 'Oman',
},
]}
/>
);
},

Write it like this:
this.setState(prevState => {
let data = [...prevState.generalPermitSelectedVehicles];
data[indexes.rowIndex].origin = text;
return {generalPermitSelectedVehicles: data};
})
Why its failing in your case?
Because when you do:
[...arr, (arr[index].origin=10)]
It will do two things, first it will update the value of origin at that index, second it will add 10 (returned 10 from ()) at the end of array also.
Check this snippet:
let arr = [{a:1}, {a:2}, {a:3}];
arr = [...arr, (arr[1].a=500)]; //500 will get added in the last
console.log('new value', arr);
Suggestion: Use updater function (prevState) because next state (value) of generalPermitSelectedVehicles is dependent on previous value.
Check the DOC for more details about setState updater function.

You need to update the original state and not append it. You are not using spread operator correctly. Also make use of functional setState when you want to update state based on prevState. You would need to do
this.setState(
prevState => ({
generalPermitSelectedVehicles: [
...prevState.generalPermitSelectedVehicles.slice(0, index.rowIndex),
{...prevState.generalPermitSelectedVehicles[
indexes.rowIndex
], origin: text},
...prevState.generalPermitSelectedVehicles.slice(index.rowIndex + 1)
],
},
() => {
console.log({
generalPermitSelectedVehicles: this.state
.generalPermitSelectedVehicles,
});
},
);
The error in your approach is that you are appending the updated state after spreading the original state, you need to update the existing instead.
Also check this answer on how to update nested state

Related

React Rest API issue

I´m new to REST API and on the current project where I can create gallery categories and images inside the gallery I bumped into a problem.
I have multiple objects within the parent array as seen below. Some of them containing image.
[]
0:
0: {path: "test", name: "test"}
1: {path: "puli", image: {…}, name: "puli"}
2: {path: "animalsg", name: "animalsg"}
3: {path: "animals", image: {…}, name: "animals"}
4: {path: "Animals", image: {…}, name: "Animals"}
5: {path: "sdfsf", name: "sdfsf"}
6: {path: "viki", name: "viki"}
7: {path: "pul", image: {…}, name: "pul"}
8: {path: "testik", name: "testik"}
__proto__: Object
length: 1
__proto__: Array(0)
Is there a way to fetch all the name values from each object and also assign it an id?
Can you please help me modify this useEffect for this purpose?
useEffect(() => {
const tempAlbum = [];
fetch('http://someapi.xy/gallery')
.then(response => response.json())
.then(data => tempAlbum.push('???'));
}, [])
If I understood right, you would want to do something like this
useEffect(() => {
const tempAlbum = [];
fetch('http://someapi.xy/gallery')
.then(response => response.json())
.then(data => { data[0].forEach(
(item,i) => {
tempAlbum.push({name: item.name, id: i})
})
});
}, [])
About the id, this approach is using the same array index as id, that could work for you unless you need this id to render this array on your React application, in which case using the index is not recommended, so you could use a library like uuid to generate an id on the fly, it's very simple to use and does not require configuration.
If what you are trying to do is simply get an tempAlbum to have an array of like [{ name: 'rockers', id: '1234'}, ...] then you can do:
data.flat().forEach((x, idx) => tempAlbum.push({ name: x.name, id: idx }));
That should work for you.

Array prop returns Observer so can't access at [0]

I passed Array but got Observer here's my code:
In Component1
data() {
return {
myWords: [],
}
}
//...
await axios.post(this.serverUrl + router, {
voca: text,
category: this.buttonGroup.category.text
})
.then(res => {
this.myWords.push({
voca: this.voca,
vocaHeader: this.vocaHeader,
category: res.data.savedVoca.category,
date: res.data.savedVoca.date,
id: res.data.savedVoca._id
})
this.myWords.push({voca:"test"})
})
.catch(err => {
console.log(err)
})
In Component2
props: {
myWordsProp: {
type: Array,
default: () => ([])
},
},
mounted() {
console.log(this.myWordsProp)
console.log(this.myWordsProp[0]) //returns undefined
},
And I expected an Array but I get Observer so I can't get values from this.myWordsProp[0] why?
//this.myWordsProp
[__ob__: Observer]
0: {
category: "ETC"
date: "2018-11-21T15:31:28.648Z"
id: "5bf57a503edf4e0016800cde"
voca: Array(1)
vocaHeader: Array(1)
...
}
1: {__ob__: Observer}
length: 2
__ob__: Observer {value: Array(2), dep: Dep, vmCount: 0}
__proto__: Array
//this.myWordsProp[0]
undefined
I found a clue that when I test it outside of axios it worked as I expected.
Vue wraps data and props into reactive objects. Use vue-devtools plugin in your browser as an alternative to viewing the ugly observer in the console.
In your code, the object behaves correctly. It’s only in the console that it ‘looks’ different.
Anyway, you can also click on the ... to expand the node and get the value from the console.
https://github.com/vuejs/vue-devtools
I found a solution It's because of sending props before get data from server.
This is my whole of postVocas function It returns promise
postVocas: function (voca) {
if (!voca || voca.length < 1) return
let router = "/api/voca"
let text = ""
text += `${this.vocaHeader[0].english}, ${this.vocaHeader[0].korean}\n`
voca.forEach((x, index) => {
text += `${voca[index].english}, ${voca[index].korean}\n`
})
return axios.post(this.serverUrl + router, {
voca: text,
category: this.buttonGroup.category.text
}).then(res => {
this.myWords.push({
voca: this.voca,
vocaHeader: this.vocaHeader,
category: res.data.savedVoca.category,
date: res.data.savedVoca.date,
id: res.data.savedVoca._id
})
}).catch(err => {
console.log(err)
})
},
And await till get data from server.
This one is function where execute My postVocas function.
sendVocaToTable: async function () {
let reformedText = this.reformText(this.text)
this.voca = this.formatTextToVoca(reformedText)
await this.postVocas(this.voca)
this.$router.push({
name: 'Table',
params: {
vocaProp: this.voca,
tableHeaderProp: this.vocaHeader
}
})
},

.map is not a function - react

I'm trying to map over an API response but it gives me "TypeError: response.map is not a function".
I think it might be due to the map method getting a string instead of an array. but when I console.log it gives me an array so I can't really see where the error comes from.
Or maybe I'm accessing the API response array in a wrong way.
I've read tons of documentation and threads but still can't get what am I doing wrong.
Thanks in advance
{status: {…}, outputs: Array(1), rawData: {…}}
outputs: Array(1)
0:
created_at:"2018-08-24T19:58:44.351091715Z"
data:
concepts:Array(20)
0:{id: "ai_69gDDQgl", name: "hamburger", value: 0.9955255, app_id: "main"}
1:{id: "ai_QLn2rxmZ", name: "lettuce", value: 0.9920815, app_id: "main"}
const IngredientsList = ({ response }) => {
const items = response.map((item) =>
<ul>{item}</ul>)
return (
<div>
<p>{items}</p>
</div>
)
}
This would render the concepts array with name and value in a list?
const IngredientsList = ({ response }) => {
if (!response || !response.outputs) {
return null;
}
const items = response.outputs[0].data.concepts.map((item) => <li>name: {item.name}, value: {item.value}</li>);
return (
<div>
<ul>{items}</ul>
</div>
)
}

RXJS Observables - http call for each index value and merge result

I do a http call to get an Array with objs. And now I want to call for each objs that return me an ID another http call. After all I want to have one observable result.
So far I managed to get for each index a http call. The problem instead of one result I got multiple.
getStats(tag: string) {
return this.service.getClanByClanTag(tag)
.map(clan => {
return clan.memberList; //the arr that return the ID's
})
.switchMap((member: PlayerByMemberListType[]) => {
return member; // singleObj of the arr
})
.concatMap((singleMember) => {
return this.service.getPlayerData(singleMember.tag).map(player => {
//push data to the new arr which should return only one time
this.newArr.push({
tag: singleMember.tag,
name: singleMember.name,
warStars: player.warStars,
trophiesNightBase: singleMember.versusTrophies
});
return this.newArr;
});
});
}
This is what the console prints out after subscribing to it:
Array [ {…} ]
Array [ {…}, {…} ]
Array(3) [ {…}, {…}, {…} ]
Array(4) [ {…}, {…}, {…}, {…} ]
Array(5) [ {…}, {…}, {…}, {…}, {…} ]
...
I know I need some kind of Observable.forkJoin but I don't know how integrate it in the code.
Try something like this:
this.service.getClanByClanTag(tag)
.mergeMap(clan => clan.memberList)
.mergeMap(
member => this.service.getPlayerData(member.tag), // supposedly this returns an observable
(member, player) => ({
tag: member.tag,
name: member.name,
warStars: player.warStars,
trophiesNightBase: member.versusTrophies
})
)
.toArray()
So basically what you want to achieve is this.
Get the clan info
Using clan info from step 1, get the memberList in the clan
For each member inside the memberList, get the players
You will need to think of a way to preserve the info at step2 when before switchMap in step3. Usually we will use a Subject, but in the case if you do not want to, simply map the Observable to preserve the data:
getStats(tag: string) {
return this.service.getClanByClanTag(tag)
.map(clan => {
return clan.memberList; //the arr that return the ID's
})
.switchMap((memberList: PlayerByMemberListType[]) => {
//note that the following map is a function of javascript array, not Observable
//it returns an array
let arrayOfObservables = memberList.map(singleMember => {
this.service.getPlayerData(singleMember.tag)
//map the data so as to preserve the data of singleMember
//by creating a new object, using Object.assign
.map(playerData => {
return Object.assign({memberData: singleMember}, playerData,)
});
})
return Observable.forkJoin(arrayOfObservables);
})
.map(players => {
//players is an array of Object that is the format of {memberData:singleMember, playerData:player)
//perform Object destructuring method
return players.map(({memberData,playerData}) => {
return {
tag: memberData.tag,
name: memberData.name,
warStars: playerData.warStars,
trophiesNightBase: memberData.versusTrophies
}
})
})
}

Issues with Redux - Adding & Removing Items From State

I'm working on a shopping cart and I'm trying to wrap my head around two problems with my app:
Adding items to the store is overwriting previous items in the store:
Initial state:
const initialState = {
items: {},
showCart: false
};
Add to Cart Reducer:
Problem: This works for adding an item to the cart, but when I go to add another item in the cart, it overwrites the previous item. Why would that be / How do I preserve the items in the previous state?
let addToCartState = {...state,
items: {
[action.id]: {
id: action.id,
color: action.product_selection.color,
size: action.product_selection.size,
quantity: 1
}
},
showCart: true
}
return state.merge(addToCartState);
Remove All From Cart Reducer:
Problem: This seems to work, but I can't seem to grab data from the state map. I can't seem to call "state.cart.items" (see mapStateToProps) like I can on my other states.
let removeFromCartState = {...state,
items: {
...state.items
},
showCart: true
}
function mapStateToProps(state) {
console.log(state.cart);
console.log("🙃");
return { products: state.products, items: state.cart.items }
}
state.cart:
Map {size: 8, _root: ArrayMapNode, __ownerID: undefined, __hash: undefined, __altered: false}
size: 8
__altered: false
__hash: undefined
__ownerID: undefined
_root: ArrayMapNode
entries: Array(8)
0: Array(2)
0: "items"
1: Map
size: 0
...
^ No items now (size: 0, was 1 after the previous reducer); do I need to use something like fromJS to parse this now or should I not have to do that?
Edit - combineReducers:
import {combineReducers} from 'redux';
import app from './appReducer';
import products from './productsReducer';
import cart from './cartReducer';
import user from './userReducer';
export default combineReducers({
app: app,
products: products,
cart: cart,
user: user
});
The root of the problem is that you're treating Immutable.js objects like regular JavaScript objects instead of using the built-in Immutable.js features intended for the tasks you're performing.
Problem: This works for adding an item to the cart, but when I go to add another item in the cart, it overwrites the previous item. Why would that be / How do I preserve the items in the previous state?
Let's take a look at your code:
let addToCartState = { ...state,
items: { [action.id]: { /* ... */ } },
showCart: true
};
The spread operator (...) does a "shallow" merge. What your code is doing, essentially, is this:
let addToCartState = shallowCopy(state);
addToCartState.items = { [action.id]: { /* ... */ } };
addToCartState.showCart = true;
In other words, it "overwrites the previous item" because you're replacing the items property with a new object with only one item. One solution is to merge items yourself:
const addToCartState = { ...state,
items: { ...state.items,
[action.id]: { /* ... */ },
},
showCart: true,
};
...but since you're using Immutable.js, you shouldn't do that. You should use its built-in mergeDeep method:
function addToCart(prevState, action) {
const addToCartState = {
items: {
[action.id]: {
color: action.product_selection.color,
// ...
},
},
showCart: true,
};
return prevState.mergeDeep(addToCartState);
}
let state = Immutable.fromJS({ items: {} });
console.log('Original state:', state);
console.log('Add blue thing');
state = addToCart(state, {
id: '123',
product_selection: { color: 'blue' },
});
console.log('State is now:', state);
console.log('Add green thing');
state = addToCart(state, {
id: '456',
product_selection: { color: 'green' },
});
console.log('State is now:', state);
.as-console-wrapper{min-height:100%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>
Problem: This seems to work, but I can't seem to grab data from the state map. I can't seem to call "state.cart.items" (see mapStateToProps) like I can on my other states.
state is not a "plain" JavaScript object, it's an Immutable.Map. You can't access its values like ordinary object properties. One solution is convert it to a plain object using toJS, then retrieve its properties (and sub-properties) like usual. An alternative, which will be preferable if your state object is potentially large, is to retrieve the values using Immutable.js' get and getIn (for "deep" properties). With the latter you'll have to use toJS on the individual values if they're also Immutable objects. You can see both approaches below.
function mapStateToProps(state) {
const obj = state.toJS();
return { products: obj.products, items: obj.cart.items };
}
// or...
function mapStateToPropsAlt(state) {
return {
products: state.get('products').toJS(),
items: state.getIn(['cart', 'items']).toJS(),
};
}
const state = Immutable.fromJS({
products: [ '¯\\_(ツ)_/¯' ],
cart: {
items: {
'123': { id: '123', color: 'blue', /* ... */ },
},
},
});
console.log('mapStateToProps(state) =>', mapStateToProps(state));
console.log('mapStateToPropsAlt(state) =>', mapStateToPropsAlt(state));
.as-console-wrapper{min-height:100%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

Categories