0

I am trying to create a react component that represents a tile.

This component is just a div that's composed of a label and a checkbox.

The problem that I have is that I can click wherever on the component and the states changes like it would normally do (eg: by clicking on the component i can check or uncheck the checkbox). but when I click on the checkbox nothing happens.

Here is my newly created component code:

const Tile = ({ title }) => {
  const [selected, setSelected] = useState(false);
  useEffect(()=>{
    console.log(selected)
  },[selected])
  return (
    <>
      <div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
        <label>{title}</label>
        <input
          type="checkbox"
          checked={!!selected}
          onChange={ev=>{setSelected(curr=>!curr)}}
        ></input>
      </div>
    </>
  );
};

and here I use it in my App.js :

return (
    <Container>
      <Row>
        <Col md={4}>
          <Tile title="USA"></Tile>
          <Tile title="MOROCCO"></Tile>
          <Tile title="FRANCE"></Tile>
        </Col>
        <Col md={8}>
          <h1>Hello</h1>
        </Col>
      </Row>
    </Container>

and finally here is my css :

      body {
        padding-top: 20px;
        font-family: "Poppins", sans-serif;
        background-color: cornsilk;
      }
      .tile {
        position: relative;
        display: block;
        width: 100%;
        min-height: fit-content;
        background: bisque;
        padding: 8px;
        margin: 1px;
      }
      .tile input[type="checkbox"] {
        position: absolute;
        top: 50%;
        right: 0%;
        transform: translate(-50%, -50%);
      }

EDIT: the problem with using the htmlFor fix on the label is that the label is clickable and the checkbox is clickable but the space between them is not. I want the the whole component to be clickable

Amine Hammou
  • 100
  • 9

3 Answers3

1

You don't need the onClick on your div.

const Tile = ({ title }) => {
  const [selected, setSelected] = useState(false);
  useEffect(() => {
    console.log(selected);
  }, [selected]);
  return (
    <>
      <div className="tile" onClick={() => setSelected((curr) => !curr)}>
        <label htmlFor={title}>{title}</label>
        <input
          id={title}
          type="checkbox"
          checked={!!selected}
          onChange={(ev) => {}}
        />
      </div>
    </>
  );
};

I made a code sandbox to test: https://codesandbox.io/s/optimistic-tharp-czlgp?file=/src/App.js:124-601

John
  • 1,240
  • 9
  • 13
  • this worked perfectly. I think like @Pawel Laskowski said the problem was that two events were being fired when I click on the checkbox. the first event toggles the checkbox and the second toggles it back that why we perceived it as nothing happening. and your solution to specify that the second event cased by the onChange on the input element does nothing is working `onChange={ ev => {} }` – Amine Hammou Sep 14 '21 at 12:49
1

When you click on the checkbox, your click event is propagated and handled by both the div and the checkbox inside the div, which results in state being toggled twice and ultimately having the same value as before.

You need to remove one of the onClicks, depending on what you want to be clickable (either the whole div or just the checkbox with the label).

Clickable div:

const Tile = ({ title }) => {
  const [selected, setSelected] = useState(false);
  useEffect(() => {
    console.log(selected)
  }, [selected])
  return (
    <>
      <div className="tile" onClick={() => setSelected(curr => !curr)}>
        <label>{title}</label>
        <input
          type="checkbox"
          checked={!!selected}
        />
      </div>
    </>
  );
};

Clickable checkbox and label:

const Tile = ({ title }) => {
  const [selected, setSelected] = useState(false);
  useEffect(() => {
    console.log(selected)
  }, [selected])
  return (
    <>
      <div className="tile">
        <label htmlFor="title">{title}</label>
        <input
          id="title"
          type="checkbox"
          checked={!!selected}
          onChange={() => setSelected(curr => !curr)}
        />
      </div>
    </>
  );
};
Pawel Laskowski
  • 6,078
  • 1
  • 21
  • 35
  • I think this may be the solution to my problem.. can you please elaborate on how I can do that. I tried the event.stopImediatePropagation but to no avail – Amine Hammou Sep 14 '21 at 12:41
  • If you want the whole div to be clickable, just remove onChange from `input`. If you want only the checkbox and its label to be clickable, remove onChange from `div` and add `htmlFor={title}` to `label`. I'll update the answer. – Pawel Laskowski Sep 14 '21 at 12:44
  • 1
    Thanks this works, however removing the onChange from the input throws a warning `Warning: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.` I prefer to just fill the onChange with an function that does nothing `onChange={ev=>{}}` – Amine Hammou Sep 14 '21 at 12:58
0

Add htmlFor prop to the label and add id to the input matching that htmlFor value.

In your case Tile component would be:

const Tile = ({ title }) => {
  const [selected, setSelected] = useState(false);
  useEffect(()=>{
    console.log(selected)
  },[selected])

  return (
    <>
      <div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
        <label htmlFor={title}>{title}</label>
        <input
          id={title}
          type="checkbox"
          checked={!!selected}
          onChange={ev=>{setSelected(curr=>!curr)}}
        ></input>
      </div>
    </>
  );
};
yotke
  • 1,170
  • 2
  • 12
  • 26