I have a redux state that I need to update, it looks like this:
[
{
date: moment("2019-06-15").toObject(),
attendance: [
{
name: "Ruben Hensen",
membershipNumber: "2084700",
attending: true,
present: false
},
{
name: "Peter Petersen",
membershipNumber: "2084701",
attending: true,
present: false
},
{
name: "Karel Kootjes",
membershipNumber: "2084702",
attending: true,
present: false
},
{
name: "Niedaar Mennekes",
membershipNumber: "2084703",
attending: true,
present: false
},
]
},
...
...
...
]
My reducer tries to find the correct week so it can update it later but is unable to find the correct week inside the array.
TOGGLE_PRESENCE: (state, action) => {
console.log(state);
console.log(action);
const index = state.findIndex(week => week.date === action.payload.date);
console.log(index);
}
The problem is that I use Redux Starter Kit and it uses Immer inside reducers. If I run my reducer this is the result of the console logs.
It's not able to find the correct week.
Any tips or pointers would be appreciated. I don't really understand Immer or the Proxy objects it uses.
Edit:
#markerikson solved the problem. I changed this:
const index = state.findIndex(week => week.date === action.payload.date);
to this:
const index = state.findIndex(week =>moment(week.date).isSame(action.payload.date));
And now it finds the correct index.
The moment instances are not going to be === equal to each other. You probably need to use Moment's APIs for comparing them, like:
const index = state.findIndex(week => week.date.isSame(action.payload.date));
Note that this part doesn't have anything to do with Immer or Redux Starter Kit specifically - it's that different object instances are going to have different references.
Related
My situation is the following:
I have an array of game objects stored as an atom, each game in the array is of the same type and structure.
I have another atom which allows me to store the id of a game in the array that has been "targeted".
I have a selector which I can use to get the targeted game object by searching the array for a match between the game ids and the targeted game id I have stored.
Elsewhere in the application the game is rendered as a DOM element and calculations are made which I want to use to update the data in the game object in the global state.
It's this last step that's throwing me off. Should my selector be writable so I can update the game object? How do I do this?
This is a rough outline of the code I have:
export const gamesAtom = atom<GameData[]>({
key: 'games',
default: [
{
id: 1,
name: 'Bingo',
difficulty: 'easy',
},
{
id: 21,
name: 'Yahtzee',
difficulty: 'moderate',
},
{
id: 3,
name: 'Twister',
difficulty: 'hard',
},
],
});
export const targetGameIdAtom = atom<number | null>({
key: 'targetGameId',
default: null,
});
export const targetGameSelector = selector<GameData | undefined>({
key: 'targetGame',
get: ({ get }) => {
return get(gamesAtom).find(
(game: GameData) => game.id === get(selectedGameIdAtom)
);
},
// This is where I'm getting tripped up. Is this the place to do this? What would I write in here?
set: ({ set, get }, newValue) => {},
});
// Elsewhere in the application the data for the targetGame is pulled down and new values are provided for it. For example, perhaps I want to change the difficulty of Twister to "extreme" by sending up the newValue of {...targetGame, difficulty: 'extreme'}
Any help or being pointed in the right direction will be appreciated. Thanks!
Let say I have an initial state tree that looks like this:
{
users: [],
items: []
}
In some cases, the result of calling the items API endpoint may result in a state tree like this:
{
users: [],
items: [
{itemId: 100, itemName: "Something100"},
{itemId: 101, itemName: "Something101"}
]
}
In other cases where there are not items to display, the state tree after an API call will be identical to the initial state tree.
Now in my component I'm using useEffect, something like this:
useEffect(() => {
if (items.length === 0) {
actions.loadItems().catch((error) => {
alert("Loading items failed! " + error);
console.log(error);
});
}
}, [items , actions]);
In this particular case, the length of items will be 0 in two cases: initial state or in case there are no results. If the API returns zero items and items.length === 0, then the action to call the API is executed repeatedly.
We really need a way of knowing that the empty array is the initial state or not. Of course I could change the state tree to something like:
{
users: {isLoaded: false, records: []},
items: {isLoaded: false, records: []},
}
That is going to add a bunch of overhead and refactoring and may not be most efficient/effective, so can someone give me a recommendation?
Unfortunately you will need some way of tracking the initialisation. If the issue is having to refactor then you can pull out this initialisation state into a higher order in the object from what you suggested. This will avoid refactoring so much:
{
isUsersLoaded: false,
isItemsLoaded: false,
users: [],
items: []
}
Another alternative is to init like this and check if users !== null etc.:
{
users: null,
items: null
}
I have an issue with updating the immutable redux and quite nested data. Here's an example of my data structure and what I want to change. If anyone could show me the pattern of accessing this update using ES6 and spread operator I would be thankful.
My whole state is an object with projects (key/value pairs - here as an example only one project) that are objects with its own key (and the keys are ids as well), arrays of procedures and inside the tasks:
{ 1503658959473:
{ projectName: "Golden Gate",
projectLocation": "San Francisco",
start:"22/09/1937",
id:1503658959473,
procedures:[
{ title: "Procedure No. 1",
tasks:[
{name: "task1", isDone: false},
{name: "task2", isDone: false},
{name: "task3", isDone: false}
]
}
]
}
}
What I'm willing to do is to update one single task 'isDone' property to 'true'. It's some kind of toggling the tasks. How can I return this state with that information updated?
The action creator pass this information to reducer:
export function toggleTask(activeProject, task, taskIndex) {
return {
type: TOGGLE_TASK,
payload: {
activeProject,
task,
taskIndex
}
};
}
You've run into a common issue with Redux. The docs recommend that you flatten your data structure to make it easier to work with, but if that's not what you want to do, I'd refer to this part of their docs.
Because both Object.assign() and the ...spread operator create shallow copies, you must go through each level of nest in your object and re-copy it.
Your code might look something like this...
function updateVeryNestedField(state, action) {
return {
...state,
procedures : {
...state.procedures,
tasks : {
return tasks.map((task, index) => {
if (index !== action.taskIndex) {
return task
}
return {
...task,
task.isDone: !task.isDone
}
}
}
}
}
}
I myself would create a new class called ProjectModel, which has a public method toggleTask that is able to update its task's status. The reducer state would be an object whose keys are project IDs and values are ProjectModel instances.
I've read that Immutable.js only make sense if you have a deep tree comparison to make. So I am assuming in the case where my application state looks like this:
const taskList = [
{
name: 'task 1',
priority: '1',
isDone: false
},
{
name: 'task 2',
priority: '1',
isDone: false
},
{
name: 'task 3',
priority: '1',
isDone: false
}
];
It's not very useful and it should look something like this to make it useful:
{
"stuff": {
"onetype": [
{"id":1,"name":"John Doe"},
{"id":2,"name":"Don Joeh"}
],
"othertype": {"id":2,"company":"ACME"}
},
"otherstuff": {
"thing": [[1,42],[2,2]]
}
}
So that we can use shallow comparison like:
shouldComponentUpdate(nextProps) {
return (this.props.name !== nextProps.name || this.props.priority !== nextProps.priority || this.props.isDone !== nextProps.isDone );
}
instead of traversing the tree, which is expensive. But otherwise, is there any reason to use Immutable.js? The above works with taskList just fine. In which case is this really needed?
EDIT:
I have been using lodash and I just heard that lodash takes mutability in mind, but I am not sure how it does the same thing as Immutable.js without immutables.
toggleTask(task) {
const found = _.find(this.state.taskList, task => task.name === task);
found.isDone = !found.isDone;
this.setState({ taskList: this.state.taskList });
}
If your taskList which you are rendering is large and elements get updated very frequently, using immutableJS objects will prevent you from renrendering all the list elements.
For example. Lets say, there is a list of 1000 tasks, which is rendered on the page.Now you made another server poll (or push), and you get the same 1000 tasks with one of the tasks property isDone changed. So, if you simply replace the old tasksList array with new tasksList array, all react components will rerender as every item in the list is a new element and shallow compare fails and this, all lifecycle methods of each list item component gets triggered. But if your taskList was an Immutable List, then you do a taskList.mergeDeep(newTaskList), only the reference of the List and the one element that has updated is changed. Thus every other list item will not go past shallow compare except the task item that has changed.
Suppose I have a reducer defined which returns an array of objects which contain keys like an id or something. What is the a redux way of getting /finding a certain object with a certain id in the array. The array itself can contain several arrays:
{ items:[id:1,...],cases:{...}}
What is the redux way to go to find a record/ node by id?
The perfect redux way to store such a data would be to store them byId and allIds in an object in reducer.
In your case it would be:
{
items: {
byId : {
item1: {
id : 'item1',
details: {}
},
item2: {
id : 'item2',
details: {}
}
},
allIds: [ 'item1', 'item2' ],
},
cases: {
byId : {
case1: {
id : 'case1',
details: {}
},
case2: {
id : 'case2',
details: {}
}
},
allIds: [ 'case1', 'case2' ],
},
}
Ref: http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
This helps in keeping state normalized for both maintaining as well as using data.
This way makes it easier for iterating through all the array and render it or if we need to get any object just by it's id, then it'll be an O(1) operation, instead of iterating every time in complete array.
I'd use a library like lodash:
var fred = _.find(users, function(user) { return user.id === 1001; });
fiddle
It might be worth noting that it is seen as good practice to 'prefer objects over arrays' in the store (especially for large state trees); in this case you'd store your items in an object with (say) id as the key:
{
'1000': { name: 'apple', price: 10 },
'1001': { name: 'banana', price: 40 },
'1002': { name: 'pear', price: 50 },
}
This makes selection easier, however you have to arrange the shape of the state when loading.
there is no special way of doing this with redux. This is a plain JS task. I suppose you use react as well:
function mapStoreToProps(store) {
function findMyInterestingThingy(result, key) {
// assign anything you want to result
return result;
}
return {
myInterestingThingy: Object.keys(store).reduce(findMyInterestingThingy, {})
// you dont really need to use reduce. you can have any logic you want
};
}
export default connect(mapStoreToProps)(MyComponent)
regards