I've looked in stack, but I couldn't find the answer to my question, if It's a duplicate I'm really sorry.
I have an array of objects:
export const people = [
{
id: 1,
name: "Thom"
},
{
id: 2,
name: "Bob"
}
];
I am importing this file:
import {people} from '../Person';
I'm assigning the state:
state = {
peopleData: people
}
And I'm trying to destruct this array like this, but unfortunately, I'm not allowed to do that:
const{people} = this.state.peopleData;
What is the reason that I'm not able to iterate with map function over my people?
{people.map(person=> (<Person key={person.id} personInfo={person}/>))}
I can do like that of course, but I don't want to, because If I would need to iterate couple more times I would need always to write the same code this.state.peopleData...:
{
this.state.peopleData.map(person=> (<Person key={person.id} personInfo={person}/>
))}
I'm assigning the state:
state = {
peopleData: people
}
And I'm trying to destruct this array like this, but unfortunately, I'm not allowed to do that:
const{people} = this.state.peopleData;
It would be either this, without destructuring:
const people = this.state.peopleData;
or this with destructuring:
const {peopleData: people} = this.state;
...which says "take the value of the property peopleData from this.state and put it into the constant people.
Related
I have an object that looks something like this:
const state = []
const spells = [
{
name: 'test',
channelDuration: 1000,
castDuration: 1000,
cast: () => {
state.push(THIS) // <--
}
}, {...}, {...}
]
When a player uses a spell, that spell gets stored into the game state and then waits for the channelDuration and castDuration to pass.
Once able to cast, it looks into the state and calls the cast method:
State.quest.bossFightState[USER].spell.cast()
state = [Spell]
On said cast, i need to push the spell into the a new array called statuses. However, i can't reference the object within the cast method without using some additional helper functions to grab the specific spell. And I dont want to pass add a parameter spell into the cast method because that just seems dumb.
Any ideas? Or is a helper method the way to go here.
I'm surprised nobody else caught this... You're using this inside of an arrow function. The this keyword acts differently in arrow functions - read more about that here
const state = [];
const spells = [
{
name: 'test',
cast: function () {
state.push(this);
},
},
];
spells[0].cast();
console.log(state);
One option would be to use a function or method instead of an arrow function, so that .cast will have a this referring to the outer object.
const state = []
const spells = [
{
name: 'test',
cast() {
state.push(this)
}
}
]
spells[0].cast();
console.log(state);
I need to set state on nested object value that changes dynamically Im not sure how this can be done, this is what Ive tried.
const [userRoles] = useState(null);
const { isLoading, user, error } = useAuth0();
useEffect(() => {
console.log(user);
// const i = Object.values(user).map(value => value.roles);
// ^ this line gives me an react error boundary error
}, [user]);
// This is the provider
<UserProvider
id="1"
email={user?.email}
roles={userRoles}
>
The user object looks like this:
{
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
I need to grab the roles value but its parent, "website.com" changes everytime I call the api so i need to find a way to search for the roles.
I think you need to modify the shape of your object. I find it strange that some keys seem to be fixed, but one seems to be variable. Dynamic keys can be very useful, but this doesn't seem like the right place to use them. I suggest that you change the shape of the user object to something like this:
{
name: "GGG",
site: {
url: "website.com",
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
In your particular use case, fixed keys will save you lots and lots of headaches.
You can search the values for an element with key roles, and if found, return the roles value, otherwise undefined will be returned.
Object.values(user).find(el => el.roles)?.roles;
Note: I totally agree with others that you should seek to normalize your data to not use any dynamically generated property keys.
const user1 = {
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
const user2 = {
name: "GGG",
friends: {},
otherData: {}
}
const roles1 = Object.values(user1).find(el => el.roles)?.roles;
const roles2 = Object.values(user2).find(el => el.roles)?.roles;
console.log(roles1); // ["SuperUser"]
console.log(roles2); // undefined
I would recommend what others have said about not having a dynamic key in your data object.
For updating complex object states I know if you are using React Hooks you can use the spread operator and basically clone the state and update it with an updated version. React hooks: How do I update state on a nested object with useState()?
I understand that variable names are not intrinsic properties of an object and thus cannot be retrieved when passed through functions. But here is my use-case and I'd like to create a mapping between members of UniversityEnums and displayStrings to get the display string.
const UniversityEnums = {
studentStatus: {Enrolled: 'Enrolled', OnHold: 'OnHold', Expelled: 'Expelled'},
professorStatus: {FullTime: 'FullTime', PartTime: 'PartTime', Emeritus: 'Emeritus', Expelled: 'Expelled'}
};
and
const displayStrings = {
studentStatus_Enrolled: 'Student is enrolled in the program',
studentStatus_OnHold: 'Student decided not to participate',
studentStatus_Expelled: 'Student was expelled',
professorStatus_FullTime: 'Staff member is hired fulltime',
professorStatus_PartTime: 'Staff member is hired parttime',
professorStatus_Emeritus: 'Staff member is retired',
professorStatus_Expelled: 'Staff member was expelled'};
My goal is to write a function that grabs a member of UniversityEnums and returns the corresponding display string, for example:
const expelledStudentDispStr = getDispString(UniversityEnums.studentStatus.Expelled);
console.log(expelledStudentDispStr);
// Student was expelled
The code I currently have has two input arguments and is like const expelledStudentDispStr = getDispString('studentStatus', UniversityEnums.studentStatus.Expelled); which needs the name of the enum to get the value but I am looking for an even smarter way!
Thanks in advance.
Note: that I can manipulate the enum object (for example define it with different variable names (or maybe, maybe, append other properties to it or its children). However, I CANNOT change their values because those values are used to compare those statuses against other variables. Also, the displayStrings is coming from a third party source and modifying them for me is not doable (at least easily!)
Possible workaround:
One way that comes to my mind is to modify my enum object to have names that match displayStrings keys:
const UniversityEnums = {
studentStatus: {
studentStatus_Enrolled: 'Enrolled',
studentStatus_OnHold: 'OnHold',
studentStatus_Expelled: 'Expelled'},
professorStatus: {
professorStatus_FullTime: 'FullTime',
professorStatus_PartTime: 'PartTime',
professorStatus_Emeritus: 'Emeritus',
professorStatus_Expelled: 'Expelled'}
};
Your
getDispString(UniversityEnums.studentStatus.Expelled)
is the same as
getDispString("Expelled")
and it would still be the same with your new object as
getDispString(UniversityEnums.studentStatus.studentStatus_Expelled)
you don't give it more information, on the other hand you could transform your enum like that
const UniversityEnums = {
studentStatus: {
Enrolled: {
status: 'Enrolled',
entity: 'student'
},
OnHold: {
status: 'OnHold',
entity: 'student'
},
Expelled: {
status: 'Expelled',
entity: 'student'
}
}
};
so you would give the extra information you need
you could do something like
for (const kind in UniversityEnums)
for (const value in UniversityEnums[kind])
UniversityEnums[kind][value] = {
kind: kind,
value: value
}
from Bergi's answer
Note that I can manipulate the enum object
In that case, it's easy: just put unique values in the enums so that you can distinguish them properly. For example,
for (const kind in UniversityEnums)
for (const value in UniversityEnums[kind])
UniversityEnums[kind][value] = kind + '_' + value;
With that you can write
function getDispString(enumValue) {
return displayStrings[enumValue];
}
I cannot seem to find an answer on here that is relevant to this scenario.
I have my state in my React component:
this.state = {
clubs: [
{
teamId: null,
teamName: null,
teamCrest: null,
gamesPlayed: []
}
]
}
I receive some data through API request and I update only some of the state, like this:
this.setState((currentState) => {
return {
clubs: currentState.clubs.concat([{
teamId: team.id,
teamName: team.shortName,
teamCrest: team.crestUrl
}]),
}
});
Later on I want to modify the state value of one of the properties values - the gamesPlayed value.
How do I go about doing this?
If I apply the same method as above it just adds extra objects in to the array, I can't seem to target that specific objects property.
I am aiming to maintain the objects in the clubs array, but modify the gamesPlayed property.
Essentially I want to do something like:
clubs: currentState.clubs[ index ].gamesPlayed = 'something';
But this doesn't work and I am not sure why.
Cus you are using concat() function which add new item in array.
You can use findIndex to find the index in the array of the objects and replace it as required:
Solution:
this.setState((currentState) => {
var foundIndex = currentState.clubs.findIndex(x => x.id == team.id);
currentState.clubs[foundIndex] = team;
return clubs: currentState.clubs
});
I would change how your state is structured. As teamId is unique in the array, I would change it to an object.
clubs = {
teamId: {
teamName,
teamCrest,
gamesPlayed
}
}
You can then update your state like this:
addClub(team) {
this.setState(prevState => ({
clubs: {
[team.id]: {
teamName: team.shortName,
teamCrest: teamCrestUrl
},
...prevState.clubs
}
}));
}
updateClub(teamId, gamesPlayed) {
this.setState(prevState => ({
clubs: {
[teamId]: {
...prevState.clubs[teamId],
gamesPlayed: gamesPlayed
},
...prevState.clubs
}
}));
}
This avoids having to find through the array for the team. You can just select it from the object.
You can convert it back into an array as needed, like this:
Object.keys(clubs).map(key => ({
teamId: key,
...teams[key]
}))
The way I approach this is JSON.parse && JSON.stringify to make a deep copy of the part of state I want to change, make the changes with that copy and update the state from there.
The only drawback from using JSON is that you do not copy functions and references, keep that in mind.
For your example, to modify the gamesPlayed property:
let newClubs = JSON.parse(JSON.stringify(this.state.clubs))
newClubs.find(x => x.id === team.id).gamesPlayed.concat([gamesPlayedData])
this.setState({clubs: newClubs})
I am assuming you want to append new gamesPlayedData each time from your API where you are given a team.id along with that data.
I have associative array.
It's a key(number) and value(object).
I need to keep state of this array same as it is I just need to update one object property.
Example of array:
5678: {OrderId: 1, Title: "Example 1", Users: [{UserId: 1}, {UserId: 2}, {UserId: 3}]}
5679: {OrderId: 2, Title: "Example 2", Users: [{UserId: 1}, {UserId: 2}, {UserId: 3}]}
I need to update Users array property.
I tried this but it doesn't work:
ordersAssociativeArray: {
...state.ordersAssociativeArray,
[action.key]: {
...state.ordersAssociativeArray[action.key],
Users: action.updatedUsers
}
}
This is data inside reducer.
What I did wrong how to fix this?
Something that might help.
When I inspect values in chrome I check previous value and value after execution of my code above:
Before:
ordersAssociativeArray:Array(22) > 5678: Order {OrderId: ...}
After:
ordersAssociativeArray: > 5678: {OrderId: ...}
Solution (code in my reducer)
let temp = Object.assign([], state.ordersAssociativeArray);
temp[action.key].Users = action.updatedUsers;
return {
...state,
ordersAssociativeArray: temp
}
So this code is working fine.
But I still don't understand why? So I have solution but would like if someone can explain me why this way is working and first not?
If it could help here how I put objects in this associative array initialy:
ordersAssociativeArray[someID] = someObject // this object is created by 'new Order(par1, par2 etc.)'
What you are doing is correct, as demonstrated by this fiddle. There may be problem somewhere else in your code.
Something that I would recommend for you is to separate your reducer into two functions, ordersReducer and orderReducer. This way you will avoid the excessive use of dots, which may be what caused you to doubt the correctness of your code.
For example, something like:
const ordersReducer = (state, action) => {
const order = state[action.key]
return {
...state,
[action.key]: orderReducer(order, action)
}
}
const orderReducer = (state, action) => {
return {
...state,
Users: action.updatedUsers
}
}
I hope you find your bug!
Update
In your solution you use let temp = Object.assign([], state.ordersAssociativeArray);. This is fine, but I thought you should know that it is sometimes preferable to use a {} even when you are indexing by numbers.
Arrays in javascript aren't great for representing normalized data, because if an id is missing the js array will still have an undefined entry at that index. For example,
const orders = []
array[5000] = 1 // now my array has 4999 undefined entries
If you use an object with integer keys, on the other hand, you get nice tightly packed entries.
const orders = {}
orders[5000] = 1 // { 5000: 1 } no undefined entries
Here is an article about normalizing state shape in redux. Notice how they migrate from using an array in the original example, to an object with keys like users1.
The problem can be that you're using array in the state but in the reducer you're putting as object. Try doing:
ordersAssociativeArray: [ //an array and not an object
...state.ordersAssociativeArray,
[action.key]: {
...state.ordersAssociativeArray[action.key],
Users: action.updatedUsers
}
]
It will put ordersAssociative array in your state and not an object.