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 storymapMethod 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.