5

I made a PoC to see how to handle my change detection on a dynamic list of checkboxes (note, i do not know beforehand how many checkboxes i have.) I created a n ES6 map (Dictionary) that tracks the checked state of each individual checkbox. but for some reason I get the following error:

 A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component

Usually when my number of form input fields is known i track them via the state, but how would one handle this case. The logic works fine, but I need to get rid of the error.

My app code:

    import React, { Component } from "react";
    import Checkbox from "./checkbox";

    class App extends Component {
        constructor(props) {
            super(props);

            this.state = {
                checkedItems: new Map()
            };

            this.handleChange = this.handleChange.bind(this);
        }

        handleChange = e => {
            const item = e.target.name;
            const isChecked = e.target.checked;
            this.setState(prevState => ({
                checkedItems: prevState.checkedItems.set(item, isChecked)
            }));
        };

        deleteCheckboxState = (name, checked) => {
            const updateChecked = typeof checked === "undefined" ? true : false;
            this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
        };

        clearAllCheckboxes = () => {
            const clearCheckedItems = new Map();
            this.setState({ checkedItems: clearCheckedItems });
        };

        render() {
            const checkboxes = [
                {
                    name: "check-box-1",
                    key: "checkBox1",
                    label: "Check Box 1"
                },
                {
                    name: "check-box-2",
                    key: "checkBox2",
                    label: "Check Box 2"
                },
                {
                    name: "check-box-3",
                    key: "checkBox3",
                    label: "Check Box 3"
                },
                {
                    name: "check-box-4",
                    key: "checkBox4",
                    label: "Check Box 4"
                }
            ];

            const checkboxesToRender = checkboxes.map(item => {
                return (
                    <label key={item.key}>
                        {item.name}
                        <Checkbox
                            name={item.name}
                            checked={this.state.checkedItems.get(item.name)}
                            onChange={this.handleChange}
                            type="checkbox"
                        />
                    </label>
                );
            });

            const checkboxesDeleteHandlers = checkboxes.map(item => {
                return (
                    <span
                        key={item.name}
                        onClick={() =>
                            this.deleteCheckboxState(
                                item.name,
                                this.state.checkedItems.get(item.name)
                            )
                        }
                    >
                        {item.name} &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                    </span>
                );
            });

            return (
                <div className="App">
                    {checkboxesToRender}
                    <br /> {checkboxesDeleteHandlers}
                    <p onClick={this.clearAllCheckboxes}>clear all</p>
                </div>
            );
        }
    }

    export default App;

The checkbox reusable component:

import React from "react";

class Checkbox extends React.Component {
    render() {
        return (
            <input
                type={this.props.type}
                name={this.props.name}
                checked={this.props.checked}
                onChange={this.props.onChange}
            />
        );
    }
}

export default Checkbox;

enter image description here

Rodney Wormsbecher
  • 663
  • 1
  • 10
  • 25

2 Answers2

5

The problem is that the checked state is initially undefined, which is a falsy value, but interpreted as not provided.

So you can simply ensure that the falsy state will actually be false by using !!.

So change the line

checked={this.state.checkedItems.get(item.name)}

to this

checked={!!this.state.checkedItems.get(item.name)}

the_cheff
  • 2,962
  • 2
  • 11
  • 19
  • This works thank you, I never knew the boolean value check for other people her eis a reference: https://stackoverflow.com/questions/784929/what-is-the-not-not-operator-in-javascript – Rodney Wormsbecher Mar 20 '19 at 11:15
2

React gives you this warning because it likes that you chose between controlled and uncontrolled components.

In the case of a checkbox input the component is considered controlled when its checked prop is not undefined.

I've just given a default value for checked and changed the code testing for undefined a little bit.

The warning should be gone.

    // import React, { Component } from "react";

    class Checkbox extends React.Component {
        static defaultProps = {
            checked: false
        }
        render() {
            return (
                <input
                    type={this.props.type}
                    name={this.props.name}
                    checked={this.props.checked}
                    onChange={this.props.onChange}
                />
            );
        }
    }

    class App extends React.Component {
        constructor(props) {
            super(props);

            this.state = {
                checkedItems: new Map()
            };

            this.handleChange = this.handleChange.bind(this);
        }

        handleChange = e => {
            const item = e.target.name;
            const isChecked = e.target.checked;
            this.setState(prevState => ({
                checkedItems: prevState.checkedItems.set(item, isChecked)
            }));
        };

        deleteCheckboxState = (name, checked) => {
            const updateChecked = checked == null ? true : false;
            this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
        };

        clearAllCheckboxes = () => {
            const clearCheckedItems = new Map();
            this.setState({ checkedItems: clearCheckedItems });
        };

        render() {
            const checkboxes = [
                {
                    name: "check-box-1",
                    key: "checkBox1",
                    label: "Check Box 1"
                },
                {
                    name: "check-box-2",
                    key: "checkBox2",
                    label: "Check Box 2"
                },
                {
                    name: "check-box-3",
                    key: "checkBox3",
                    label: "Check Box 3"
                },
                {
                    name: "check-box-4",
                    key: "checkBox4",
                    label: "Check Box 4"
                }
            ];

            const checkboxesToRender = checkboxes.map(item => {
                return (
                    <label key={item.key}>
                        {item.name}
                        <Checkbox
                            name={item.name}
                            checked={this.state.checkedItems.get(item.name) || false}
                            onChange={this.handleChange}
                            type="checkbox"
                        />
                    </label>
                );
            });

            const checkboxesDeleteHandlers = checkboxes.map(item => {
                return (
                    <span
                        key={item.name}
                        onClick={() =>
                            this.deleteCheckboxState(
                                item.name,
                                this.state.checkedItems.get(item.name)
                            )
                        }
                    >
                        {item.name} &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                    </span>
                );
            });

            return (
                <div className="App">
                    {checkboxesToRender}
                    <br /> {checkboxesDeleteHandlers}
                    <p onClick={this.clearAllCheckboxes}>clear all</p>
                </div>
            );
        }
    }
    ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
remix23
  • 2,261
  • 1
  • 8
  • 18