Why can I not spread an array of objects with Zustand? - javascript

I am using Zustand in a Next.js app using TypeScript,
For some reason, I get a runtime error message whenever I try to iterate on my state object.
The structure of my car damaged zone object is:
const damagedZones = {
Left: {
front_door:[
{
id: 1,
picture1: 'picture 1 base64 string',
picture2: 'picture 2 base64 string',
comment: 'any comment'
},
{
id: 2,
picture1: 'picture 1 base64 string',
picture2: 'picture 2 base64 string',
comment: 'any comment'
}
],
back_door: [
],
so, imagine I add a new object to my "front_door" array, here is my zustand store and function:
In the code below, the dynamic prop "zone" would be my "Left" key of my damagedZones object, and the dynamic prop "element" would be my "front_door" key.
export const useDamagedZones = create<DamagedZonesProps>((set) => ({
damagedZones: damagedZones,
setDamagedZones: (elementItem: damagedItem, zone: string, element: string) => {
set(state => ({
damagedZones: {
...state.damagedZones,
[zone]: {
...state.damagedZones[zone],
[element]: [
...state.damagedZones[zone]?.[element],
elementItem
]
}
}
}))
},
}))
so basically when I trigger this function, I get a runtime error which says:
TypeError: Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a Symbol.iterator method.
I am not understanding why that is so....
I have tried using an object instead of an array, with an id as key, and it works fine, but it's not super convenient, so an array is the best in this situation, but it doesn't perform as expected....

Alright i figured it out, it is because i misconceived my typescript types for my damagedZone object ! i forgot to mention that one key would be an array :)
it works now that my object type is like so :
type damagedItem = {
id?: number
picture1?: string | null,
picture2?: string | null,
picture3?: string | null,
comment?: string,
}
type DamagedElement = {
[key: string]: damagedItem[]
}
type DamagedZone = {
step1_gauche: DamagedElement,
step1_droite: DamagedElement,
step1_avant: DamagedElement,
step1_arriere: DamagedElement
}
and here is the store useDamagedZones for now :
export const useDamagedZones = create<DamagedZonesProps>((set) => ({
damagedZones: damagedZones,
setDamagedZones: (elementItem: damagedItem, zone: string, element: string) => {
set(state => ({
damagedZones: {
...state.damagedZones,
[zone]: {
...state.damagedZones[zone],
[element]: [
...state.damagedZones[zone]?.[element],
elementItem
]
}
}
}))
},
removeDamagedItem : (zone: string, element: string, id: number ) => {
set(state => ({
damagedZones: {
...state.damagedZones,
[zone]: {
...state.damagedZones[zone],
[element]:
state.damagedZones[zone]?.[element].filter(el => el.id !== id)
}
}
}))
}
}))

Related

Angular 12 how to return an http get request array of objects and assign to other array of different type using map()

Hey I'm trying to implement a bootstrap5 dropdown following this example: Creating Multi-Select Dropdown with Angular and Bootstrap 5
In that example, to get the data, he uses an app.service and just returns an array of objects:
getFoods(): Food[] {
return [
{
id: 1,
name: 'Grapes'
},
{
id: 2,
name: 'Melon'
},
...
And then in his ngOnInit() calls the getFoods() method and also uses .map() operator because he has to assign to values because the item model has two values:
ngOnInit(): void {
this.items = this.appService.getFoods().map(fruit => ({
id: fruit.id,
name: fruit.name
} as Item));
}
So I'm trying to do hat but with data being fetched from an API endpoint using HTTP GET request.
But I don't know how to use the .map() operator for the http get request:
this.subscription = this.contactSearchService.currentCodes.pipe(
map(
code => (
{
id: code.code,
name: code.code
}
)
)).subscribe(Response => {
this.items = Response
})
It's giving me these errors:
Property 'code' does not exist on type 'ResponsibilityCode[]'.
Type '{ id: any; name: any; }' is missing the following properties from type 'Item[]': length, pop, push, concat, and 26 more.
My http get request function:
private _reponsibilityCodeSource = new BehaviorSubject<ResponsibilityCode[]>([]);
currentCodes = this._reponsibilityCodeSource.asObservable();
getCodes(): void {
this.http.get<ResponsibilityCode[]>('https://localhost:44316/api/SITEContacts/ResponsibilityCode').subscribe(Response => {
this._reponsibilityCodeSource.next(Response);
});
}
I get the data as `JSON` btw.
The rxjs pipe(map(.... code...)) is different than array.map -
pipe(map()) does not operate on each item of an array
So the errors you are getting is because you're swapping out the array of ResponsibilityCode for a single item (code in your code is all the responsibility codes)
Try
this.subscription = this.contactSearchService.currentCodes.subscribe(Response => {
this.items = Response.map(
code => (
{
id: code.code,
name: code.code
}
)
)
})
Your HTTP get returns an Observable of ResponsibilityCode array, so to achieve that you have to map (Array.prototype.map) the items of the array within the RxJS's map operator, like the following:
this.subscription = this.contactSearchService.currentCodes
.pipe(
map((res: ResponsibilityCode[]) =>
res.map((item) => ({
id: item.id,
name: item.name,
}))
)
)
.subscribe((res) => {
this.items = res;
});

Typescript does not allow to access a property dynamically

I have some issues when I try to access my object property in typescript:
const total = (type: string) => {
return {
status: 'Total',
test: data?[type].total,
};
};
total('first')
This is how my data looks:
data: {
first: {
total: 15
},
second: {
total: 515
}
}
Trying to access the property i get TS2339: Property 'total' does not exist on type 'string[]'. Why i get this and how to solve the issue?
You are looking for for optional chaining operator here and
data?.[type] is correct syntax.
Also TS will throw an error if just try to read object field with object[property]. It assumes correctly that it's not possible to read object property with just any string, it requires more specifics types. Have a look at code below.
interface Data {
first: {
total: number
},
second: {
total: number
}
}
const data: Data = {
first: {
total: 15
},
second: {
total: 515
}
}
const total = (type: keyof Data) => {
return {
status: 'Total',
test: data?.[type].total
}
}
total('first')
Working snippet

React typescript Property 'map' does not exist on type 'User'

I am trying to use react with typescript. I have initialized useState with an object but can't use map function with that object.
Here is the error I am getting
Property 'map' does not exist on type 'User'
Here is the code.
Thank you in advance
interface User {
name : string,
email : string,
stream_key : string,
}
const App = () => {
const [liveStreams, setLiveStreams] = useState<User>({
name : '',
email : '',
stream_key : ''
})
// setting livestreams
const getStreamsInfo = (live_streams) => {
axios.get('http://192.168.43.147:4000/streams/info', {
}).then(res => {
console.log(res.data)
setLiveStreams({
name: res.data.name,
email: res.data.email,
stream_key: res.data.stream_key
})
});
}
return (
{liveStreams.map(data => <Text>{data.email}</Text>)}
)
You only have a single User object, not an array of them. Either:
Just use the object (and ideally use the singular rather than the plural for the name of the state variable), or
Use an array of objects instead.
With #1, for instance, if you used the name liveStream, it would look like this:
return <Text>{liveStream.email}</Text>;
Given that you've said
Actually res.data contains data of multiple users. How to use array of objects using typescript? Sorry for newbie questio
in a comment, it looks like you want #2, which means:
Make your initial data an empty array.
const [liveStreams, setLiveStreams] = useState<User[]>([]);
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^−−^^
When you receive the list, use the whole list:
const getStreamsInfo = (live_streams) => {
axios.get('http://192.168.43.147:4000/streams/info', {
}).then(res => {
// To *replace* state with the list in `res.data`:
setLiveStreams(res.data);
// >>OR<< to **add to** the current state, appending the list from `res.data`:
setLiveStreams(streams => [...streams, ...res.data]);
});
}
Add key attributes to your Text elements, since they're entries in an array.
In your case, the actual solution will be to use a type guard to check that you have an array before attempting to use the map method.
if (liveStreams instanceof Array) {
liveStreams.map(data => <Text>{data.email}</Text>);
}
interface User {
name : string,
email : string,
stream_key : string,
}
const App = () => {
const [liveStreams, setLiveStreams] = useState<User[]>([{
name : '',
email : '',
stream_key : ''
}])
// setting livestreams
const getStreamsInfo = (live_streams) => {
axios.get('http://192.168.43.147:4000/streams/info', {
}).then(res => {
console.log(res.data)
// is the `res.data` an Array Object?
// or u may just do like that
/*
* const { data } = res
* // if `data` type is user[]
* setLiveStreams(data)
*/
setLiveStreams([{
name: res.data.name,
email: res.data.email,
stream_key: res.data.stream_key
}])
});
}
return (
{liveStreams.map(data => <Text>{data.email}</Text>)}
)

How to compare obj of array1 with obj of array in javascript

I'm stuck with the code. I actually want to filter elements in dataLayer using typescript/javascript.
I have dataLayer defined as shown below
track: { products },
dataLayers: { current: { cart: { items } } }
products?: IProduct[]
export interface IProduct {
id: string
quantity?: string
name?: string
}
items?: ICartItem[]
export interface ICartItem {
id: string
brand: string
name: string
quantity: number
}
track: { products }
products have {id,quantity}
dataLayers: { current: { cart: { items } } }
items have {id, brand, name, quantity }
Now I want to filter id and get the name of the product, For Example:
Example:
*
products:{
[{id: 'a123',quantity: '1'},{id:'a345', quantity:'2'}]
}
items:{
[{id: 'a123',brand:'pen',name: 'Reynolds', quantity: '1'}, {id: 'a143',brand:'pencil',name: 'Nataraj', quantity: '3'}, {id: 'a122',brand:'pen',name: 'Parker',quantity: '1'},{id:'a345',brand:'Eraser',name: 'Faber-Castell', quantity:'2'}]
}*
Expected output
id:a123,name:'Reynolds'
id:a345,name:'Faber-Castell'
my code:
const id = products.map(product => { return product.id})
items.filter((item) => {
return item.id === id
})
.map((item)=> {
const { id, name } = item
console.log("id" + id)
console.log("name" + name)
})
Actual output
Giving error
**
const id: string[]
This condition will always return 'false' since the types 'string' and 'string[]' have no overlap.ts(2367)
**
Why am I not able to compare item.id with id
You are comparing the string array id with each item id which is a string. Do like below.
items.filter(item => id.includes(item.id))
In the first line of your code, you are fetching the array of ids from products array. Hence id is nothing but an array.
const id: string[] = products.map(product => { return product.id })
Therefore, the below code will do the job.
items.filter((item) => id.includes(item.id) )
Also, If you are coding in typescript, develop the habit of using variable with its types. [number, string, boolean, string[] ...]

In Typed JS (Typescript/FlowType) how do you handle normalized objects?

Working on a project. I'm starting with flow type because it's easier to implement piecemeal but eventually I plan to convert from flow to Typescript when we move from "proof of concept" into "prototype". However, a solution to this problem in either should work in both flow or TS.
I'm writing a backend API which makes queries to a database.
Now, my query to the DB gives me this:
type OneMeeting = {
location: string
participants: Array<string>
}
const RawDataFromDB: Array<OneMeeting> = await getDataFromDB();
Here's the problem:
I want to consolidate that data, so that if all participants are identical, the zip codes are combined.
So, I want this:
type Meeting = {
locations: Array<string>
participants: Array<string>
}
const RawDataFromDB: Array<OneMeeting> = [
{
location: "Trump Tower",
participants: ["Kushner", "Trump Jr.", "Manifort", "Veselnitskaya"]
},
{
location: "Mar A Lago",
participants: ["Kushner", "Trump Jr.", "Manifort", "Veselnitskaya"]
},
{
location: "Mar A Lago",
participants: ["Trump Sr.", "Abramovich"]
}
]
const WhatIWantAtTheEnd: Array<Meeting> = [
{
locations: ["Trump Tower", "Mar A Lago"],
participants: ["Kushner", "Trump Jr.", "Manifort", "Veselnitskaya"]
},
{
locations: ["Mar A Lago"],
participants: ["Trump Sr.", "Abramovich"]
}
]
Now, the way I had been converting from Raw Data to What I want was basically to sort() the participants in each meeting, create an object where the key is the JSON.stringified version of the participants array, and push the location values. So there's an intermediate step where, instead of an array of meetings, there's an intermediate Object with an unknown number of keys, where the names of those keys cannot be determined in advance.
And for the life of me, I can't figure out how to type out that intermediate Object so that it doesn't throw a type error, without making it an "any" - which will then throw errors if I try to .sort() on an "any" value.
So, typescripterinos, how would you approach this?
-- Edit, this is how I normally would do the conversion from A->B.
const getWhatIWant = (rawData: OneMeeting[]): Meeting[] => {
// what is the type I should use for normalized ?
let normalized: Object = rawData.reduce((pv: Object, curr: OneMeeting) => {
let key = curr.participants.sort().join(",")
if(!pv[key]){
pv[key] = {locations: [curr.location], participants: curr.participants}
} else {
pv[key].locations.push(curr.location)
}
return pv;
}, {})
return Object.values(normalized);
}
From what I understand of the algorithm you describe, the intermediate object type you are looking for, should have an indexer from string to Meeting defined:
let map: { [participants: string]: Meeting } = {};
for (let m of RawDataFromDB) {
let key = m.participants.sort().join(',');
let existingMeeting = map[key];
if (!existingMeeting) {
map[key] = {
locations: [m.location],
participants: m.participants
}
} else {
existingMeeting.locations.push(m.location);
}
}
Or using reduce as you do in your sample, you just need to specify the indexable type as a generic parameter (the parameter representing the result type) to reduce
const getWhatIWant = (rawData: OneMeeting[]): Meeting[] => {
// what is the type I should use for normalized ?
let normalized = rawData.reduce<{ [key: string]: Meeting }>((pv, curr) => {
let key = curr.participants.sort().join(",")
if (!pv[key]) {
pv[key] = { locations: [curr.location], participants: curr.participants }
} else {
pv[key].locations.push(curr.location)
}
return pv;
}, {})
return Object.values(normalized);
}
Not sure if it's what you really want, but here is a one liner code:
// Assuming that type Meeting = { location: string[]; participants: string[]; }
const WhatIWantAtTheEnd: Meeting[] = RawDataFromDB
.map(meeting => meeting.participants.sort((a, b) => a.localeCompare(b))) // Create a participant sorted list
.filter((value, index, array) => index === array.findIndex(compValue => value.every(participant => compValue.includes(participant)))) // strip duplicates
.map(sortedParticipants => ({
participants: sortedParticipants,
location: RawDataFromDB.filter(raw => raw.participants.every(participant => sortedParticipants.includes(participant))).map(e => e.location)
}));

Categories