Calculate state recursively using map/reduce? - javascript

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

Related

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.

Redux: cloning state does not work

I have a very simple question, but…
The code (in a redux/react-native app) of a reducer:
...
case SAMPLES_DELETE_REQUEST_SUCCESS: {
var newState = Object.assign({}, state);
const indexToDelete = newState.samples.findIndex( sample => {
return sample.id == action.sample.id
})
newState.samples.splice(indexToDelete, 1)
debugger;
return newState
}
...
Ok, I copy the state and store it into newState. But when I do newState.samples.splice(indexToDelete, 1), newState is correctly modified, but also state! Why?? I must be tired…
splice function modifies original array. Object.assign does not do deep cloning. Therefore you are still modifying the original state!
You will have to manually copy the nested object(or array) you want to clone:
// Deep Clone
obj1 = { a: 0 , b: { c: 0}};
let obj2 = JSON.parse(JSON.stringify(obj1));
As someone mention before you could use JSON.parse(JSON.stringify(obj)) to create a new copy of the entire object (nested object as well). If you don't want to do that, you could check libraries like Inmutable JS
Also if you want to use spread notation, a better way to do that will be:
return {
...state,
samples: state.samples.filter(sample => sample.id !== action.sample.id)
}

reactjs changing object attributes inside the state object

I get an object as response from a get.
Now I assign the object to a state var (object) like this:
this.setState({editData: response.data})
Is there a way to change the values within this object?
I thought about something like this:
this.setState({editData.[var]: [value]})
thanks
Firstly you have to remember that you should never mutate state object directly. So first you shold make a copy of state object and mutate the copy. Then set this copy as state. You can use spread syntax to achieve it:
this.setState((prevState) => ({editData: {...prevState.editData, [var]: value}}));
Here is working example showing that source object is not mutated:
let state = {
editData: {
age: 22
}
};
let age = "age";
let stateCopy = {editData: {...state.editData, [age]: 100}};
console.log(state);
console.log(stateCopy);

State mutated by getter

I'm trying to write a getter that returns a simple number from state, decremented by 1
const getters = {
getCurrentView: state => {
return types.PAGES_OBJECT[state.currentViewNum]
},
getCurrentViewNum: state => {
return state.currentViewNum--
}
};
However this actually mutates the state.
I have tried assigning it to a var, but it appears that var becomes a direct reference to state.
How do I do this, without mutating state?
Isn't x-- equivalent to x = x - 1?
The -- operator is mutating your object.
return state.currentViewNum - 1 instead

Categories

Resources