javascriptreactjsset

Using a Set data structure in React's state


Is it possible to use ES6's Set data structure in React?

For example, if I have a checklist composed of distinct items, and I'd like to maintain each item's checked state. I'd like to write something like this:

export default class Checklist extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      checkedItems: new Set()
    }
  }

  addItem(item) {
    //...?
  }

  removeItem(item) {
    //...?
  }

  getItemCheckedStatus(item) {
    return this.state.checkedItems.has(item);
  }

  // More code...
}

I understand there may be a problem with the fact that a Set is mutable by nature, and React performs a shallow comparison when updating the component, so it expects immutable objects to be passed and held in the state. However, is there a way to hold and maintain a Set object in the state?


Solution

  • Since react will identify state changes only if the state property was replaced, and not mutated (shallow compare), you'll have to create a new Set from the old one, and apply the changes to it.

    This is possible since new Set(oldSet) !== oldSet.

    const oldSet = new Set([1, 2]);
    const newSet = new Set(oldSet);
    
    console.log(oldSet === newSet);


    How to use a set with the useState() hook.

    The example adds random integers between 1-5, and then removes random number in the same range.

    const { useState } = React;
    
    const Comp = () => {
      const [state, setState] = useState(() => new Set());
    
      const addItem = item => {
        setState(prev => new Set(prev).add(item));
      }
    
      const removeItem = item => {
        setState(prev => {
          const next = new Set(prev);
    
          next.delete(item);
    
          return next;
        });
      }
    
      return (
        <div>
          <button onClick={() => addItem(Math.ceil(Math.random() * 5))}>Add</button>
          <button onClick={() => removeItem(Math.ceil(Math.random() * 5))}>Remove</button>
          <div>
            {Array.from(state)}
          </div>
        </div>
      );
    }
    
    ReactDOM
      .createRoot(root)
      .render(<Comp />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <div id="root"></div>


    How you use a Set in a class component:

    export default class Checklist extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          checkedItems: new Set()
        }
        
        this.addItem = this.addItem.bind(this);
        this.removeItem = this.removeItem.bind(this);
      }
    
      addItem(item) {
        this.setState(({ checkedItems }) => ({
          checkedItems: new Set(checkedItems).add(item)
        }));
      }
    
      removeItem(item) {
        this.setState(({ checkedItems }) => {
          const newChecked = new Set(checkedItems);
          newChecked.delete(item);
          
          return {
           checkedItems: newChecked
          };
        });
      }
    
      getItemCheckedStatus(item) {
        return this.state.checkedItems.has(item);
      }
    
      // More code...
    }