0

I made a custom checkbox control made from a regular div/span.

Because the <input type='checkbox'.../> cannot have children element, so I created the another one from a div with role='checkbox' and aria-checked={isChecked}.
Then i handled the mouse & keyboard event for toggling the check state.

The question is:
How to trigger 'change' event? The code below doesn't work in react 16

refTarget?.current?.dispatchEvent(new Event('change', { bubbles: true, cancelable: false }))

The another trick is calling the props.onChange directly.
It works but the event doesn't bubble to parent <form onChange={(e) => console.log('changed!')}></form>

Heyyy Marco
  • 633
  • 1
  • 12
  • 22

1 Answers1

0

You have 2 ways to achieve this.

1. create all events dynamically.

eventTarget.dispatchEvent won't work in React. check this question for alternative approach and more information on this.

2. Receive onChange events from the input

You can receive keep the real input element hidden by opacity, and make it span full width/height of your element. Then you only have to listen onChange on this input.

This is how material-ui does it

example

const { useCallback, useState, useRef } = React;

const MyCheckBox = React.forwardRef((props, ref) => {
  const { onChange, onBlur, onFocus, ...otherProps } = props;
  
  const [checked, setChecked] = useState(false);
  
  const handleFocus = useCallback((e) => {
    // do something,
    // like add some className for effects
    if (typeof onFocus === 'function')
      onFocus(e)
  }, [])
  const handleBlur = useCallback((e) => {
    // do something,
    // like remove some className for effects
    if (typeof onBlur === 'function')
      onBlur(e)
  }, [])
  
  const handleChange = useCallback((e) => {
    setChecked(e.target.checked);
    if (typeof onChange=== 'function')
      onChange(e)
  }, []);
  
  return <div onFocus={handleFocus} onBlur={handleBlur} className="checkbox-root">
  {checked ? 'Checked' : 'Not checked'}
    <input onChange={handleChange} ref={ref} type="checkbox" className="checkbox-input" {...otherProps} />
  </div>
})

const App = (props) => {
 return <div>
 <MyCheckBox onChange={e => console.log('change', e.target.checked)} onFocus={e => console.log('focus')} onBlur={e => console.log('blur')} />
 </div>;
}

ReactDOM.render(<App />, document.querySelector('#root'))
.checkbox-root {
  position: relative;
  height: 40px;
  width: 40px;
  border: 1px solid red;
}
.checkbox-input {
  position: absolute;
  opacity: 0.1;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  margin: 0;
}

.hidden-input {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.production.min.js"></script>

<div id="root"/>
Avinash Thakur
  • 1,640
  • 7
  • 16
  • but layering a transparent real input causing the parent element lost focus. there are already a focus/blur indication on that element. – Heyyy Marco Jul 14 '21 at 22:18
  • the custom event works if listening from vanilla js addEventListener, but doesn't work on React's onChange={ } . Works on react v15 but doesn't on v16 – Heyyy Marco Jul 14 '21 at 22:28
  • @HeyyyMarco updated the answer. dispatchEvent won't work in react. You can read the attached question for more info. – Avinash Thakur Jul 14 '21 at 22:30