DEV Community

Nikolay
Nikolay

Posted on

Improper state management in ReactJS

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:

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

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.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay