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.
Related
I came up with the following questing while I was trying to make a TODO app using React.js
I was trying to make and update functionality for each task. For that, I made a button which on click, calls a function "update" which calls a function passed as a prop defined in App.js, this function iterates through the tasks array find the one to modify and does its thing, then it sets the new state
App.js
function App() {
const [taskItems, settaskItems] = useState([]);
const updateTask = (task,newname) =>{
const findAndModify=()=>{
let tasks = [...taskItems] // Why let task=taskItems doesnt work?
for (let index = 0; index < tasks.length; index++) {
const t = tasks[index];
if (t.name === task.name){
t.name = newname
}
}
return tasks
}
settaskItems(findAndModify())
}
}
My TaskRow component is the following
<TaskRow task={task} key={task.id} toggleTask={toggleTask} updateTask={updateTask}></TaskRow>
Inside the definition of my TaskRow.js component I have the following code
export const TaskRow = (props) => {
const update = () => {
const newname = "hello";
props.updateTask(props.task, newname);
};
return (
<tr key={props.task.id}>
<td>{props.task.name}</td>
<td>
<input
type="checkbox"
checked={props.task.done}
onChange={() => props.toggleTask(props.task)}
/>
</td>
<td>
<button className="btn btn-success mx-1" onClick={update}>
Actualizar
</button>
</td>
</tr>
);
};
the question comes here, if in findAndModify instead of doing let tasks = [...taskItems] I do task=taskItems the description of the updated task does not re-render until another change happens, but if a do a console.log of the data, it indeed, had been modified
why is that? how does the [...] operator differs of a regular variable assignment?
I've researched about destructuring in JS but nobody talks about some kind of difference between the use of this operator and a regular var assignment in React.js
The difference between:
(1) method:
let tasks = [...takItems];
(2) method:
tasks = taksItems
is the behavior of javascript with Objects type (for ex. Array)
when you assign a variable to an array it creates a reference and not a shallow copy.
So if you use the (2) method and change tasks variable, it will be reflected in tasksItems, if you use the (1) method, you will have 2 independence variables!
React indicates to NEVER mutate a state directly, like you did when using the (2) method and treat it as immutable type.
You can read more about it here
Why we should never update React state directly
To expand a little bit more, the spread operator [...takItem] makes a shallow copy, which means that it will be a new copy of the variable, but because it is 'shallow' any nested non-primitive data will all be pointers. E.g.,
const tasks = ['a', 'b', { subtask: 1 }]
const shallowCopy = [...tasks]
shallowCopy[0] = 'A'; // won't change the value of tasks
shallowCopy[2].subtasks = 'C'; // does change the values of tasks
console.log(tasks)
// ['a', 'b', 'C']
In the example above, the third item in the tasks array is non-primitive (also known as "reference" type), so shallowCopy[2] is just a pointer to the same place in memory as tasks[2]. tasks[2] and shallowCopy[2] are both referencing the same piece of data, which is why when you modify shallowCopy[2].subtasks it also modifies tasks[2].subtasks
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.
I would like to create an object of arrays converting the single level key - (string) value relation to key - (array) keys collection.
Basically, the code must collect other keys and their values recursively starting from collecting self. At the end the object must be like this;
{
ROLE_SUPER_ADMIN: [
'ROLE_SUPER_ADMIN',
'ROLE_ADMIN',
'ROLE_MODERATOR',
'ROLE_AUTHOR'
]
}
What i have achieved yet is;
export const roles = {
ROLE_SUPER_ADMIN: 'ROLE_ADMIN',
ROLE_ADMIN: 'ROLE_MODERATOR',
ROLE_MODERATOR: 'ROLE_AUTHOR',
ROLE_AUTHOR: null,
ROLE_CLIENT: null
}
export function roleMapper() {
const roleArray = {}
const mapper = (key) => {
roleArray[key] = [key];
if (!roles[key] || Array.isArray(roles[key])) {
return;
} else if (!roles[roles[key]]) {
roleArray[key].push(roles[key])
} else {
if (roleArray.hasOwnProperty(key)) {
Object.keys(roles).filter(r => r !== key).forEach((role) => {
roleArray[key].push(mapper(role))
})
}
}
}
Object.keys(roles).forEach((key) => {
mapper(key)
});
console.log(roleArray);
}
I have completely lost solving this. Please help, thanks.
I would use a function generator for this, taking advantage of the easy recursion approach and taking advantage of Object.entries combined with Array.map.
The below method acquires all the siblings of a defined key from an object, assuming that each key value may be the child of the said key.
As a side note, you could technically do that in many other ways (without relying on function generators), I just think that the generator approach is clever and easier to maintain. Moreover, it allows you to re-use the method later and allows you to eventually iterate the values.
Code explanation is directly in the code below.
const roles = {
ROLE_SUPER_ADMIN: 'ROLE_ADMIN',
ROLE_ADMIN: 'ROLE_MODERATOR',
ROLE_MODERATOR: 'ROLE_AUTHOR',
ROLE_AUTHOR: null,
ROLE_CLIENT: null
}
// Acquire all the siblings, where a sibling is a key whose value is the value of another key.
function* getSiblings(v, source) {
// if the desired key exists in source..
if (source[v]) {
// yield the value, which is a role in that case.
yield source[v];
// next, yield all the siblings of that value (role).
yield* [...getSiblings(source[v], source)];
}
}
// Map all roles by its siblings.
const res = Object.entries(roles).map(([key, role]) => {
// key is the main role, whereas role is the "child" role.
// Technically, [key] is not exactly a child role of [key], so we're injecting it manually below to avoid polluting the getSiblings method.
return {
[key]: [key, ...getSiblings(key, roles)] // <-- as mentioned above, the array is build by starting from the main role (key) and appending the child roles (siblings). [key] is a shorthand to set the key.
}
});
console.log(res);
I would separate out the recursive call necessary to fetch the list from the code that builds the output. That allows you to make both of them quite simple:
const listRoles = (rolls, name) => name in roles
? [name, ... listRoles (rolls, roles [name] )]
: []
const roleMapper = (roles) => Object .assign (
... Object.keys (roles) .map (name => ({ [name]: listRoles (roles, name) }))
)
const roles = {ROLE_SUPER_ADMIN: 'ROLE_ADMIN', ROLE_ADMIN: 'ROLE_MODERATOR', ROLE_MODERATOR: 'ROLE_AUTHOR', ROLE_AUTHOR: null, ROLE_CLIENT: null}
console .log (
roleMapper (roles)
)
Here listRoles is the recursive bit, and it simply takes a roles object and a name and returns all the descendant names, so
listRoles(roles, 'ROLE_MODERATOR') //=> ["ROLE_MODERATOR", "ROLE_AUTHOR"]
roleMapper uses that function. It takes the roles object and calls listRoles on each of its keys, combining them into a new object.
Together, these yield the following output:
{
ROLE_SUPER_ADMIN: ["ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_ADMIN: ["ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_MODERATOR: ["ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_AUTHOR: ["ROLE_AUTHOR"],
ROLE_CLIENT: ["ROLE_CLIENT"]
}
I see the accepted answer generates a structure more like this:
[
{ROLE_SUPER_ADMIN: ["ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_ADMIN: ["ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_MODERATOR: ["ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_AUTHOR: ["ROLE_AUTHOR"]},
{ROLE_CLIENT: ["ROLE_CLIENT"]}
]
(The difference is that mine was a single object, versus this one which was an array of single-property objects.)
While that feels less logical to me, it would be even easier to write:
const roleMapper = (roles) => Object.keys (roles) .map (n => ({ [n]: listRoles (roles, n) }))
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,
}
]
}))
});
}
}
I have an array of objects, let's say
let objs: foo[];
an immutable state object
let state: State;
and a function
transform: (state: State, obj: foo) => State;
So transform calculates a new State from the previous one using the information from the current obj; if you're thinking 'sounds like Redux to me' you could be right.
Is there a way to call transform recursively for each object from the array such, that each calculated state is the input parameter for the next call to transform using map and reduce? I'm only interested in the final state.
I tried something like
let finalState = objs.reduce((prev: State, curr: foo) => transform(prev, curr), state)
but as reduce requires prevand curr to be of type foo (the array type) this is not going to work obviously.
You can use the current index provided by Array.prototype.reduce:
return objs.reduce((prev, next, index) => transform(prev, objs[index]), state);
But this way you're kind off abusing the reduce function. Imo this should and could be done with a regular forEach statement:
function getFinalState(state: State, objs: Obj[]) : State {
objs.forEach(x => state = transform(state, x));
return state;
}
See this TypeScript playground for working samples