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>

1 Answer 1

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"/>

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

3 Comments

but layering a transparent real input causing the parent element lost focus. there are already a focus/blur indication on that element.
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
@HeyyyMarco updated the answer. dispatchEvent won't work in react. You can read the attached question for more info.

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.