Improper state management in ReactJS
Tags: react
Published: 2023-05-23 17:24:27

Let's consider a simple example to illustrate the losing state problem in a React application when storing a generic class instance in a component's state.

Create a generic class called `Counter`:

```javascript
class Counter {

  constructor() {
    // Setting shared state of the class
    this.count = 0;
  }

  increment() {
    // Changing shared state of the class
    this.count += 1;
  }

  getCount() {
    return this.count;
  }
}
```

Now, let's create a React component that uses this `Counter` class:

```javascript
import React, { useState } from 'react';

function CounterComponent() {
  // Initializing instance of the Counter class 
  //and save it to **ReactJS state**
  const [counterInstance, setCounterInstance] = useState(new Counter());

  const handleIncrement = () => {
    // Let's increment the counter and save it to the react state
    counterInstance.increment();
    // What can go wrong, huh?
    setCounterInstance(counterInstance);
  };

  return (
    <div>
      <h1>Count: {counterInstance.getCount()}</h1>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
}

export default CounterComponent;
```

In this example, we have a `CounterComponent` that initializes and stores an instance of the `Counter` class in its state. When the user clicks the "Increment" button, the `handleIncrement` function is called, which increments the count and updates the state with the modified `counterInstance`.

The problem with this implementation is that React's state updates are asynchronous, and it uses a shallow merge to update the state. As a result, the `Counter` class instance **is not handled correctly**, and you may experience issues with the component's behavior or state updates.

To fix this problem, let's refactor the code to store the count value as a plain JavaScript object and keep the logic related to the `Counter` class separate:

```javascript
import React, { useState, useEffect, useRef } from "react";

// Pretty much the same class but with one important change
class Counter {
  constructor(initialCount) {
    // Instance will be initialized only once
    console.log("init");
    this.count = initialCount;
  }

  increment() {
    // The method returns value to be saved in react state,
    // not in the shared state of the instance
    this.count += 1;
    return this.count;
  }
}

function CounterComponent() {
  const [counterInstance, setCounterInstance] = useState();
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Save the instance of the Counter class to the react state
    // It is better to use proper state management library here
    setCounterInstance(new Counter(0));
  }, []);

  const handleIncrement = () => {
    const newCount = counterInstance.increment();
    setCount(newCount);
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
}

export default CounterComponent;
```

Now we use `useEffect` to create the `Counter` class instance only once when the component mounts. Then, you store the `Counter` class instance in the React state with `setCounterInstance`.

### Conclusion:
To avoid issues with asynchronous state updates and shallow merging in React, it's better to store simple data types in the state, rather than class instances, and manage complex logic separately.

**However**, it's important to note that storing class instances in React state is generally not a best practice. It's more common to keep state as simple as possible, typically using JavaScript primitives and plain objects.