Let me ask you a question
this.state.cart.push(item.id);
this.setState({ cart: this.state.cart });
Copy the code
Is there a difference between the two? Everyone familiar with React knows to avoid manipulating state directly, but why? Today we’re going to explore that.
This refers to Immutability, a common concept in Javascript. Take a look at the following code:
const items = [
{ id: 1, label: 'One' },
{ id: 2, label: 'Two' },
{ id: 3, label: 'Three'},];return( <div> <button onClick={() => items.push({id:5})}>Add Item</button> {items.map( item => <Item key={item.id} {... item} />)} </div> )Copy the code
Clicking on a button triggers a click event to add a new item to items, but the page does not re-render to display the added item on the page. This is because the array is a reference type. When push is used to modify the array, react’s state management reference to the array does not change. Therefore, when React performs a dirty check and compares the array, it cannot determine that the array has changed. Also, when we write code like this.state. Something = x or this.state = x, we get all kinds of weird bugs that affect component performance. All because the original object was mutate. Therefore, to avoid this problem, it is necessary to ensure Immutability of the object.
What is Immutability
If you haven’t encountered this concept before, it’s easy to confuse it with assigning a variable to a new value or reassigning a value. “Immutable” is exactly the opposite of “mutable”, we know that to be mutable, modifiable… Can be messed with. So immutable means that you cannot change the internal structure or values of data. For example, some array operations in Javascript are immutable (they return a new array, not modifying the original array). String operations are always immutable (they create a new string containing changes).
Immutable Objects
We want to make sure that our object is immutable. You need to use a method that must return a new object. Essentially, we need something called a pure function. Pure functions have two properties:
- The return value must depend on the input value, and the return value will not change if the input value does not change
- It does not have an impact beyond its scope
What is “influence beyond its scope”? This is a slightly broad definition, and it means modifying something that is not within the scope of the current functionality. Such as:
- Modify the value of an array or object directly, as in the previous example
- Modify state outside the current function, such as global variables, window properties
- API request
- Math.random()
Methods like the following are pure functions:
function add(a, b) {
return a + b;
}
Copy the code
No matter how many times you execute it, as long as the input values of A and B remain the same, its return value will never change and it will have no other effect.
An array of
We create a pure function to handle array objects.
function itemAdd(array, item) {
return [...array, item]
}
Copy the code
We use the extension operator to return a new array without changing the internal structure and data of the original array. Or we could do it the other way, copy an array and then do something.
function itemAdd(array, item) {
const newArr = array.concat();
// const neArr = array.slice();
// const newArr = [...array];
return newArr.push(item);
}
Copy the code
object
By using object.assign (), you create a function that returns a new Object without changing the content or structure of the original Object.
const updateObj = (data, newAttribute) => {
return {
Object.assign({}, data, {
location: newAttribute
})
}
}
Copy the code
We can also use the extended algorithm:
const updateLocation = (data, newAttribute) => {
return {
...data,
location: newAttribute
}
}
Copy the code
How to update state in React and Redux
For a typical React application, its state is an object. Redux is also the foundation for using immutable objects as application storage. This is because React will not know how to update the virtual DOM if it cannot determine that the component’s state has changed. The immutability of objects makes it possible to track these changes. React compares the old state of an object to its new state and rerenders the component based on that difference. React and Redux also apply to arrays and objects.
React updates objects
In React you use the this.setState() method, which implicitly merges the objects you pass in using object.assign (), so for state changes: in Redux
return {
...state,
(updates here)
}
Copy the code
And in the React
this.setState({
updates here
})
Copy the code
** But it is important to note that ** although the setState() method implicitly merges objects, updating deeply nested items in state (anything deeper than the first level) is handled using the extended operator methods of objects (or arrays).
Update objects in Redux
When you want to update a top-level attribute in a Redux state object, copy the existing state with the extension operator, and then list the attribute to change with the new value.
function reducer(state, action) {
/*
State looks like:
state = {
clicks: 0,
count: 0
}
*/
return {
...state,
clicks: state.clicks + 1,
count: state.count - 1
}
}
Copy the code
Redux updates nested objects
If the object to be updated is a tier (or multiple) structure in the Redux state, you need to make a copy of each level of the object to be updated. Look at the following example of a two-tier structure:
function reducer(state, action) {
/*
State looks like:
state = {
school: {
name: "Hogwarts",
house: {
name: "Ravenclaw",
points: 17
}
}
}
*/
// Two points for Ravenclaw
return {
...state, // copy the state (level 0)
school: {
...state.school, // copy level 1
house: { // replace state.school.house...
...state.school.house, // copy existing house properties
points: state.school.house.points + 2 // change a property
}
}
}
Copy the code
Redux updates objects by key value
function reducer(state, action) {
/*
State looks like:
const state = {
houses: {
gryffindor: {
points: 15
},
ravenclaw: {
points: 18
},
hufflepuff: {
points: 7
},
slytherin: {
points: 5
}
}
}
*/
// Add 3 points to Ravenclaw,
// when the name is stored in a variable
const key = "ravenclaw";
return{... state, // copy state houses: { ... state.houses, // copy houses [key]: { // update one specific house (using Computed Property syntax) ... state.houses[key], // copy that specific house's properties points: state.houses[key].points + 3 // update its `points` property } } }Copy the code
Used in the storymap
Method updates the items in an array
The.map function of the array returns a new array by calling the provided function, passing each existing item and using the return value as the value of the new item.
function reducer(state, action) {
/*
State looks like:
state = [1, 2, "X", 4); * /return state.map((item, index) => {
// Replace "X" with 3
// alternatively: you could look for a specific index
if(item === "X") {
return 3;
}
// Leave every other item unchanged
return item;
});
}
Copy the code
Immutable.js
All of the methods mentioned above are normal Javascript methods, and you can see that some of the more complex objects are too cumbersome to handle, making you want to give up. In particular, deeply nested object updates are difficult to read, write, and perform correctly. Unit tests are required, but even those tests don’t make the code easier to read and write. Fortunately, we can use a library to do this: Immutable. Js. It provides a powerful and rich API. Let’s see how it works by designing a TODO component. Let’s first introduce
import { List, Map } from "immutable";
import { Provider, connect } from "react-redux";
import { createStore } from "redux";
Copy the code
Then create some tags to define this component:
const Todo = ({ todos, handleNewTodo }) => {
const handleSubmit = event => {
const text = event.target.value;
if (event.keyCode === 13 && text.length > 0) {
handleNewTodo(text);
event.target.value = ""; }};return (
<section className="section">
<div className="box field">
<label className="label">Todo</label>
<div className="control">
<input
type="text"
className="input"
placeholder="Add todo"
onKeyDown={handleSubmit}
/>
</div>
</div>
<ul>
{todos.map(item => (
<div key={item.get("id")} className="box">
{item.get("text")}
</div>
))}
</ul>
</section>
);
};
Copy the code
We use the handleSubmit() method to create a new backlog. In this case, the user will just create a new backlog, and we only need one action:
const actions = {
handleNewTodo(text) {
return {
type: "ADD_TODO", payload: { id: uuid.v4(), text } }; }};Copy the code
We can then proceed to create the Reducer function and pass the actions created above to the Reducer function:
const reducer = function(state = List(), action) {
switch (action.type) {
case "ADD_TODO":
return state.push(Map(action.payload));
default:
returnstate; }};Copy the code
We will use Connect to create a container component that can be inserted into the storage. We then need to pass in the mapStateTops () and mapsDispatchtoprops () functions to connect the components.
const mapStateToProps = state => {
return {
todos: state
};
};
const mapDispatchToProps = dispatch => {
return {
handleNewTodo: text => dispatch(actions.handleNewTodo(text))
};
};
const store = createStore(reducer);
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Todo);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Copy the code
We used mapStateToProps() to provide the stored data for the component. The action is then bound to the component using mapsDispatchToProps() so that the action creator can use it as a prop for the component. In the Reducer function, we create the initial state of the application using a List from Immutable.
const reducer = function(state = List(), action) {
switch (action.type) {
case "ADD_TODO":
return state.push(Map(action.payload));
default:
returnstate; }};Copy the code
We treat the List as a JavaScript array, so we can use the.push() method on state. The value used to update the status is an object that indicates that the map can be recognized as an object. This ensures that the current state will not change, eliminating the need to use object.assign () or extension operators. This looks much cleaner, especially if the states are deeply nested and we don’t need to spread the extension operators out to various locations. The immutable state of an object enables code to quickly determine if a state has changed.
This concept can be a little confusing at first, but when you encounter a bug that pops up during development due to a state change, you’ll be able to understand the cause and effectively fix the problem.