Skip to content
Advertisement

Does React not work with js Set:s (the datatype)?

I’m trying to use the js Set datastructure with React but it will not update like I thought it would. Consider the following two two examples. Buttons are supposed to go green when pressed.

The first one with a Set (NOT WORKING)

let buttons = [
    "Button 1",
    "Button 2",
    "Button 3",
    "Button 4",
    "Button 5",
    "Button 6",
    "Button 7",
    "Button 8",
]

let pressedInitial = []

function MyComponent(props) {
    let [pressed, setPressed] = React.useState([])
    return <div>
        {buttons.map(button => {
            return <button
                key={button} /* To satisfy React (not relevant for this example) */
                onClick={
                    (ev) => {
                        setPressed([...pressed, button])
                    }
                }
                style={pressed.includes(button) ? { backgroundColor: "green" } : {}}
            >
                {button}
            </button>
        })}
    </div>
}

const domContainer = document.querySelector('#renderDiv');
ReactDOM.render(React.createElement(MyComponent), domContainer);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    
 <div id="renderDiv"></div>

And the same example but with an array instead (WORKING)

let buttons = [
    "Button 1",
    "Button 2",
    "Button 3",
    "Button 4",
    "Button 5",
    "Button 6",
    "Button 7",
    "Button 8",
]

let pressedInitial = new Set()

function MyComponent(props) {
    let [pressed, setPressed] = React.useState(pressedInitial)
    return <div>
        {buttons.map(button => {
            return <button
                key={button} /* To satisfy React (not relevant for this example) */
                onClick={
                    (ev) => {
                        setPressed(pressed.add(button))
                    }
                }
                style={pressed.has(button) ? { backgroundColor: "green" } : {}}
            >
                {button}
            </button>
        })}
    </div>;
}

const domContainer = document.querySelector('#renderDiv');
ReactDOM.render(React.createElement(MyComponent), domContainer);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    
 <div id="renderDiv"></div>

Advertisement

Answer

React decides whether to rerender through comparing old and new state. If they are === the component will not rerender.

.add does not create a new set; it mutates and returns the old one. This means that the old and new state will point to the same place in memory. So they will always be equal when compared.

Always avoid state mutation in React, else unexpected behavior can occur. Here, since the new component state (in the pressed state variable) is === to the old component state, no re-rendering occurs.

Make a new Set instead when setting state:

setPressed(new Set([...pressed, button]))

let buttons = [
    "Button 1",
    "Button 2",
    "Button 3",
    "Button 4",
    "Button 5",
    "Button 6",
    "Button 7",
    "Button 8",
]

let pressedInitial = new Set()

function MyComponent(props) {
    let [pressed, setPressed] = React.useState(pressedInitial)
    return <div>
        {buttons.map(button => {
            return <button
                key={button} /* To satisfy React (not relevant for this example) */
                onClick={
                    (ev) => {
                        setPressed(new Set([...pressed, button]))
                    }
                }
                style={pressed.has(button) ? { backgroundColor: "green" } : {}}
            >
                {button}
            </button>
        })}
    </div>;
}

const domContainer = document.querySelector('#renderDiv');
ReactDOM.render(React.createElement(MyComponent), domContainer);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    
 <div id="renderDiv"></div>
Advertisement