Using setState to dynamically populate an array of objects - javascript

I have an array of Objects in my props from my API but I need to modify the elements in this array of Objects and then pass it to a player on my application. I can successfully get my array of object but when I try to modify the elements and set the data into a state, The new state ends up having just the last item of my list. Here is my code,
if(musicitem.musics){
if(musicitem.musics.length === 0){
this.setState({musicLimited:true })
}
else{
return musicitem.musics.map((item, index)=>{
this.setState({
musics:[
...musics,
{
url:apiConstants.API_MUSIC_FILES+item.url,
cover:apiConstants.API_MUSIC_FILES+item.cover,
artist:item.artist,
}
]
})
});
}
}
I expect my state of musics to have an array of newly constructed objects but that is not the case. It ends up with just the last object that is expected to be in my array of objects. This means that, it has actually loop through the array of objects from props but the next State keeps overriding the previous state.

You should mention previous state's property(i.e. music) to get setState know that while using spread operator.
You can access previous state as passing first params to this.setState().
Example :
this.setState(prevState => ({ music : [...prevState.music, {artist:item.artist}] }))
Your code should be updated like this :
if(musicitem.musics){
if(musicitem.musics.length === 0){
this.setState({musicLimited:true })
}
else{
return musicitem.musics.map((item, index)=>{
this.setState(prevState => ({
musics:[
...prevState.musics, // use prevState
{
url:apiConstants.API_MUSIC_FILES+item.url,
cover:apiConstants.API_MUSIC_FILES+item.cover,
artist:item.artist,
}
]
}))
});
}
}

Related

Computed property getting called before prop initialization

In my Vue app, I am getting an array from map getters and I am trying to filter this array based on type which is a prop in a computed property. I checked both of them, both are strings, but the filter is not working properly, since I feel the computed property is called before the prop is initialized with value. Need some help with this?
all: 'mdm/all', // here 'mdm' indicates module name and 'all' is the state
prop: [type]
Inside computed, I have a property called
getData() {
const filteredData = this.all.filter(ele => ele.type === this.type.toLowerCase());
return filteredData.map(item => (
{name: item.name,
orderNo: item.order_no
});
}
In the above code, both ele.type and this.type seems to be strings with similar value (say 'expired') but the filteredData always happens to be an empty array.
Not sure what could be cause for this.
I would debug what .filter() is doing with a console log inside of it, for example:
this.all.filter(ele => {
console.log(`${ele.type} === ${this.type.toLowerCase()}?`, ele.type === this.type.toLowerCase());
return ele.type === this.type.toLowerCase();
})`
which will print out the result of ele.type === this.type.toLowerCase() every loop so you can verify what exactly is happening.

Redux modifying the a state value without being asked to?

i'm using redux to manage my state , my initial state in the reducer contains to arrays !
the first one(games) is the one that i want to modify , the second one (InitialGames) is Initial one that i don't want it to be modified !
the problem is i only make changes on the first array ! but when i console my state after the logic ! i see that both arrays got changed ??? which is confusing !
The case that i'm going into is PLAYER_DEAD
My Reducer
import { ADD_GAME, PLAYER_DEAD, PUT_INFOS, RESET_GAME } from "./actions";
const initialState = {
games: [],
InitialGames: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case RESET_GAME:
state.games[action.payload.gameIndex] =
state.InitialGames[action.payload.gameIndex];
console.log(state);
return state;
case ADD_GAME:
return {
games: [...state.games, action.payload.game],
InitialGames: [...state.games, action.payload.game],
};
case PUT_INFOS:
return {
gameInfos: action.gameInfos,
};
case PLAYER_DEAD:
let newGames = state.games;
let newInitialGames = state.InitialGames;
console.log("Before Changings", newGames, newInitialGames);
let newTeam = newGames[action.payload.indexGame].teams[
action.payload.index
].players.splice(0, 1);
console.log(
"After changings",
newGames,
newInitialGames
);
return {games:newGames,InitialGames:newInitialGames};
}
return state;
};
This would be occurring because you're passing the same action.payload.game object to both of your arrays here:
return {
games: [...state.games, action.payload.game],
// same objects ---------^------v
InitialGames: [...state.games, action.payload.game],
}
When you access the .teams array in your PLAYER_DEAD case, you're accessing the same array in memory shared by both games and InitialGames, the same goes for anything within that array, including the .players array within your .teams array's objects. Because you're updating your array in place in a non-immutable way by using .splice(), you end up modifying your state directly and thus modifying the same .players array referenced by both games and InitialGames.
You need to ensure that you don't modify your state in place by using methods like .splice(). For your particular case, you would do something like so:
const newGames = state.games.map((game, i) => i === action.payload.indexGame
? game.teams.map((team, j) => j === action.payload.index
? {...team, players: team.players.slice(1)} // note slice, not splice
: team
)
: game
);
Above, we map your arrays, updating the items when the inndx matches the item to update. When updating the players array, we use .slice() to remove the first item from the array.
Writing immutable code isn't always easy, that's why redux toolkit has built-in support for immer when you use an API such as createSlice() API that will allow you to write code like you've been doing the mutates your state. See here for more info.

How to update a value in destructing and loop

What I try to achieve:
I want to update a value in an obj, which is part of the element of array. See the code below will give you better idea.
There is an issue that I update the value of object, via reference, instead of making a copy. This causes the state behave strangely.
I try to change it to making a copy, but I am not sure.
e.g.
const returnObj = {
...objs,
fields: [{name, value}, {name, value}, {name, value_update_this_only}, ...],
};
// This is the current code
export function* onChange(action) {
// get partial state from redux state
const list = yield select((state) => state.list);
let objs = list[action.index];
// * e.g. objs.fields === [{name, value}, {name, value}, ...]
// * basically following, find the correct field and update its value
// * following has problem, beause we change the value of a reference,
// * instead we should make a new copy, so redux can react
objs.fields.map((field) => {
if (field.name === action.fieldName) {
field["value"] = action.fieldValue;
}
return field;
});
// fire to redux reducer
yield put({
type: "UPDATE",
prop: obj,
docIndex: action.index,
});
}
// the problem: I don't know how to do it in destructing manner.
const returnObj = {
...objs,
fields: [],
};
I think rather than try and come up with a single destructuring statement that makes this work, it's easier to digest (and arguably more readable) in smaller steps:
Make a shallow copy of objs; call it copy for now
Recreate fields array and every item within it
For the desired array item, update its value
Set the copy.fields to the array created in 2
// Step 1: Shallow copy
let copy = { ...objs }
// Step 2: Recreate fields and every item
let fields = copy.fields.map((field) => ({
...field
}))
// Step 3: Update value of desired item
fields.forEach((field) => {
if (field.name === action.fieldName)
field.value = action.fieldValue
})
// Step 4: Reassign fields to the copy
copy.fields = fields
Refactoring this, steps 2-4 can be combined into one step without sacrificing that much readability:
let copy = { ...objs }
copy.fields = copy.fields.map((field) => ({
...field,
value: field.name === action.fieldName ? action.fieldValue : field.value,
}))
It's been a long time since I've used redux or sagas, so I'm not sure whether fields needs to be an entirely new array or if just the changed object within fields needs to be new, but the above can be modified to accommodate either need.

Cannot assign to read only property 'property' of object '#<Object>'

I have this problem.
With interface:
export interface IDevice {
id: string,
selected: boolean
}
creating instance by:
let newDevice: IDevice = {
id: uuid4(),
selected: false,
} as IDevice;
It gets added to array in recoil state, and in React used in a function where array has been retrieved with useRecoilState().
const [leftList, setLeftList] = React.useState<IDevice[]>([]);
Now it is being used in a handler for selecting the devices on a list control, here the error occurs:
...
leftList.map((item: IDevice) => {
if (item.id === event.dataItem.id) {
item.selected = !item.selected;
}
return item;
})
...
And I get the error: Cannot assign to read only property 'selected' of object '#'
Even cloning the array first by [...leftList] does not help.
I'm lost:-) Hope someone can spread light on this?
State shouldn't be modified directly, which is what you're currently doing now in your .map() method by updating the objects. TS is trying to tell you not to do this by making your objects read-only.
Instead, you can create a new object with all the properties from item (done using the spread syntax ...), along with a new overwritting property selected, which will use the negated version of the item's currently selected item if the id matches the event's data item id, or it'll keep the original selected value:
leftList.map((item: IDevice) => ({
...item,
selected: item.id === event.dataItem.id ? !item.selected : item.selected
}))
Cloning the array using the spread syntax ([...leftList]) will only do a shallow copy, and won't do a deep-copy of the objects within it, as a result, modifying the object references within .map() is still modifying the original state.
Or, instead of spreading the item object and creating a new object each time (which can hinder performance a little as pointed out by #3limin4t0r), you can instead only return a newly created object when you want to modify the selected property:
leftList.map((item: IDevice) => {
if (item.id !== event.dataItem.id) return item;
return {...item, selected: !item.selected};
});

Rendering a single element from an array of data in React

If I was to map through an array of objects of data and only wanted to render a single element from the array based on a certain condition like below:
dataArray.map((element, i) => {
if(i === currentElement){
return (
<div>{element.data}</div>
)
} else {
return null;
}
});
Would that be acceptable? this seems to return what I am after but was curious if this was the most efficient way to go about it because this still returns an array with a length of the data array with all null elements except on the desired single element.
Using map on an array will return another array with your function performed on each element, so the function you are using is literally pushing the value 'null' into the return array for any element that doesn't pass your condition. You could just use
dataArray.map((ele, i) => {
if(i === currentElement){
return (
<div>{element.data}</div>
)
}
and the map will simply do nothing with any element that does not pass the condition.
Mapping over an array in React is usually (often?) used for creating <li> tags, and React might get cranky in your console about not having a key. If you see that, check out this link here: https://reactjs.org/docs/lists-and-keys.html
You could use the find function to get the value you want to render rather than rendering a bunch of null values.
const element = dataArray.find((el, i) => i === currentElement);
if(element) {
return (<div>{element.data}</div>);
}

Categories

Resources