Understanding state in React - reactjs

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.

Related

Apollo cache.modify update slower than setState while using in React Beautiful Drag and Drop

I'm fetching a query, and modifying the order of the list in it, using cache.modify on the drag end.
This does modify cache, as it should but, it takes milliseconds to do that.
How to reproduce the issue:
I'm using react beautiful dnd, to make drag-n-drop card.
It provides onDragEnd handler, where we can specify what happens when the user stops dragging.
In this case, I want to reorder the list on the drag end.
Cache modify:
cache.modify({
id: cache.identify(data.pod),
fields: {
stories(existingStoriesRefs, { readField }) {
return reorder(existingStoriesRefs, sourceIndex, destinationIndex);
},
},
});
Reorder logic:
const reorder = (list: any[], startIndex: number, endIndex: number) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
This is correctly working and rendering using stories in setState.
But, instead of copying Apollo data, to a new state, I think it's better to directly modify the cache.
But, using cache.modify, it works but, rendering is kind of glitchy. It seems, it first renders the existing list and then, modify cache in the next render. The glitch is around less than a second, but visible to the user.
I fixed it using cache.modify inside, mutation, and using optimistic update.
moveStoryMutation({
variables: {
id: stories[sourceIndex].id,
sourceIndex,
destinationIndex,
},
optimisticResponse: {
__typename: "Mutation",
moveStory: true,
},
update: (proxy) => {
proxy.modify({
id: proxy.identify(pod),
fields: {
stories(existingStoryRefs) {
return reorder(
existingStoryRefs,
sourceIndex,
destinationIndex
);
},
},
});

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)

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

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.

Detect when mobx observable has changed

Is it possible to detect when an observable changes in any way?
For instance, say you have this:
#observable myObject = [{id: 1, name: 'apples'}, {id: 2, name: 'banana' }]
And later on, with some user input, the values change. How can I detect this easily?
I want to add a global "save" button, but only make it clickable if that observable has changed since the initial load.
My current solution is to add another observable myObjectChanged that returns true/false, and wherever a component changes the data in the myObject, I also add a line that changes the myObjectChanged to true. And if the save button is clicked, it saves and changes that observable back to false.
This results in lots of extra lines of code sprinkled throughout. Is there a better/cleaner way to do it?
You could use autorun to achieve this:
#observable myObject = [{id: 1, name: 'apples'}, {id: 2, name: 'banana' }]
#observable state = { dirty: false }
let firstAutorun = true;
autorun(() => {
// `JSON.stringify` will touch all properties of `myObject` so
// they are automatically observed.
const json = JSON.stringify(myObject);
if (!firstAutorun) {
state.dirty = true;
}
firstAutorun = false;
});
Create an action that will push to myObject and set myObjectChanged
#action add(item) {
this.myObject.push(item);
this.myObjectChanged = true;
}
As capvidel mentioned, you can use autorun to track if variable changes, but to avoid adding additional variable firstAutorun you can replace autorun with reaction:
#observable myObject = [{id: 1, name: 'apples'}, {id: 2, name: 'banana' }]
#observable state = { dirty: false }
reaction(
() => JSON.stringify(myObject),
() => state.dirty = true
);

Resources