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

2 Answers 2

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)}

Sign up to request clarification or add additional context in comments.

1 Comment

This works thank you, I never knew the boolean value check for other people her eis a reference: stackoverflow.com/questions/784929/…
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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.