When does it make sense to use Immutable.js in React? - reactjs

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.

Related

What is recommended way to determine if a Redux state empty array is the initial state or the result of an API call that returns an empty array?

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
}

How to correctly update redux state in ReactJS reducer?

I have the following structure in my Redux data store:
{
filterData: {
22421: {
filterId: 22421,
selectedFilters: [
{
filterName: 'gender',
text: 'Male',
value: 'male'
},
{
filterName: 'gender',
text: 'female',
value: 'female'
}
]
}
22422: {
filterId: 22422,
selectedFilters: [
{
filterName: 'colour',
text: 'Blue',
value: 'blue'
},
{
filterName: 'animal',
text: 'sheep',
value: 'Sheep'
}
]
}
Can someone point me towards using the correct way to update the selectedFilters array without mutating the state directly? i.e. How can I add/remove elements in the selectedFilters array for a given filterId?
Generally it's done by using non mutating (ie. returning a new object, rather than modifying the existing one) operators and function:
spread operator (...) for objects and arrays (for additions and edits),
filtering, mapping and reduction for arrays (for edits and removals),
assigning for object (for edits and additions).
You have to do this on each level leading to the final one—where your change happens. In your case, if you want to change the selectedFilters on one of those objects you'll have to do something like that:
// Assuming you're inside a reducer function.
case SOME_ACTION:
// Returning the new state object, since there's a change inside.
return {
// Prepend old values of the state to this new object.
...state,
// Create a new value for the filters property,
// since—again—there's a change inside.
filterData: {
// Once again, copy all the old values of the filters property…
...state.filters,
// … and create a new value for the filter you want to edit.
// This one will be about removal of the filter.
22421: {
// Here we go again with the copy of the previous value.
...state.filters[22421],
// Since it's an array and we want to remove a value,
// the filter method will work the best.
selectedFilters:
state.filters[22421].selectedFilters.filter(
// Let's say you're removing a filter by its name and the name
// that needs to be removed comes from the action's payload.
selectedFilter => selectedFilter.name !== action.payload
)
},
// This one could be about addition of a new filter.
22422: {
...state.filters[22422],
// Spread works best for additions. It returns a new array
// with the old values being placed inside a new one.
selectedFilters: [
// You know the drill.
...state.filters[22422].selectedFilters,
// Add this new filter object to the new array of filters.
{
filterName: 'SomeName',
text: 'some text',
value: action.value // Let's say the value comes form the action.
}
]
},
}
}
This constant "copy old values" is required to make sure the values from nested objects are preserved, since the spread operator copies properties in a shallow manner.
const someObj = {a: {b: 10}, c: 20}
const modifiedObj = {...someObj, a: {d: 30}}
// modifiedObj is {a: {d: 30}, c: 20}, instead of
// {a: {b: 10, d: 30}, c: 20} if spread created a deep copy.
As you can see, this is a bit mundane to do. One solution to that problem would be to create some kind of nested reducers functions that will work on separate trees of the state. However, sometimes it's better not to reinvent the wheal and use tools that are already available that were made to solve those kind of problems. Like Immutable.js.
If you want to use a dedicated library for managing the immutable state (like suggested in another answer) take a look at Immer.
I find that this library is simpler to be used than Immutable.js (and the bundle size will be smaller too)

Redux updating nested immutable data

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.

Understanding state in React

I am creating an app which displays and hides UI elements on the page based on checkbox value of a 'toggler' and checkbox values of the list elements which are created from this.state.items.
The app has an initial state set as:
state = {
items: [
{
id: 1,
name: 'Water',
isComplete: false
}, {
id: 2,
name: 'Salt',
isComplete: false
}, {
id: 3,
name: 'Bread',
isComplete: false
}
],
isChecked: false,
currentItem: '',
inputMessage: 'Add Items to Shopping Basket'
}
I created the following method which filters through items and returns all of the isComplete: false, and then I set the new state with these returned items.
toggleChange = (e) => {
this.setState({isChecked: !this.state.isChecked});
if (!this.state.isChecked) {
const filtered = this.state.items.filter(isComplete);
this.setState({items: filtered})
} else {
// display previous state of this.state.items
}
}
How do I come back to the 'Previous' state when I set 'toggler' to false?
If you only need to keep the default list then keep it out of state entirely
and just keep the filtered list in the state.
This way you could always filter the original list.
You can even consider filtering in the render method itself and not keeping the filtered list in state at all.
If you need to go back before a change was made (keeping history)
you could maintain another filtered list in your state.
I am not sure what you want to do but, if you implement componentWillUpdate() (https://facebook.github.io/react/docs/react-component.html#componentwillupdate) you get access to the next state and the current state.
-nextState will be what your state is going to be, and this.state is what it is now.
You could save this.state into another variable called this.oldState before it is updated, and then refer back to it.
Nonetheless, since state doesn't keep history of itself, you might consider approaching the problem differently.

Reactjs: What is the right way to modify a object in a large array in state?

I have thousands of objects in an array stored in state, like this:
state: {
data: [{name: 'a', status: true}, {name: 'b', status:false}, ...]
}
this.state.data.length > 10000
I want to modify some status in the array, like set status from this.state.data[1000] to this.state.data[3000] to true;
I used to clone the data into a new array first, but I met some performance issue for this. Since all we have clone are the object references, when we modify the cloned array, we are still modifying the actual object. So I don't know if it is still meaningful to clone the array.
And what is the right way to do this?
React got an update helper to deal with this kind of situations
import update from 'react-addons-update'
this.setState(
{
data: update(this.state.data,{
[indexToChange] : {
status: {$set: true}
}
})
}

Resources