6

I'm trying to log the click event on a button in react:

const InputBox = () => {
  const clicky = fromEvent(
    document.getElementById('clickMe'),
    'click'
  ).subscribe(clickety => console.log({ clickety }));

  return (
    <button id="clickMe" type="button">
      Click Me
    </button>
  );
};

I get the following error 'Invalid event target'

enter image description here

The setup seems to be fine. If I replace document.getElementById('clickMe') with document then it logs the clicks. But that logs any click in the document and I just want the clicks on the button in question.

I tried using a ref instead...

const InputBox = () => {
  const buttonEl = React.useRef(null);

  const clicky = fromEvent(buttonEl.current, 'click').subscribe(clickety =>
    console.log({ clickety })
  );

  return (
    <button ref={buttonEl} type="button">
      Click Me
    </button>
  );
};

...but then I get the same 'Invalid event target' error.

Can someone help me understand why this is an invalid event target and how to fix this so that I can use fromEvent in react.


Update

The problem was that I did not register the observable when mounting the react components.

If you do this, you must also unmount the component when unmount.

This works for me now.

const InputBox = () => {
  React.useEffect(() => {
    const click$ = fromEvent(
      document.getElementById('clickMe'),
      'click'
    ).subscribe(clickety => console.log({ clickety }));
    return () => click$.unsubscribe();
  }, []);

  return (
    <button id="clickMe" type="button">
      Click Me
    </button>
  );
};
Josh Pittman
  • 7,024
  • 7
  • 38
  • 66

2 Answers2

11

Wrap it in useEffect() that's when the dom is ready

const InputBox = () => {
  const buttonEl = React.useRef(null);

  useEffect(() => {
    const clicky = fromEvent(buttonEl.current, 'click').subscribe(clickety =>
      console.log({ clickety })
    );

    return () => clicky.unsubscribe();
  }, []);

  return (
    <button ref={buttonEl} type="button">
      Click Me
    </button>
  );
};
curiouser
  • 970
  • 2
  • 12
  • 22
Fan Cheung
  • 10,745
  • 3
  • 17
  • 39
  • This worked thank you. Don't need to use refs, works with Id selector. For anyone else following this, you only need to register onMount, so keep your dependancy array empty, rather than mounting on every render. ```const InputBox = () => { React.useEffect( () => fromEvent(document.getElementById('clickMe'), 'click').subscribe( clickety => console.log({ clickety }) ), [] ); return ( ); };``` – Josh Pittman Oct 14 '19 at 07:30
  • 1
    ...and also dont forget to unsubscribe ``` const InputBox = () => { React.useEffect(() => { const click$ = fromEvent( document.getElementById('clickMe'), 'click' ).subscribe(clickety => console.log({ clickety })); return () => click$.unsubscribe(); }, []); return ( ); }; ``` – Josh Pittman Oct 14 '19 at 07:45
  • U can also use subject – Fan Cheung Oct 14 '19 at 07:46
  • I don't want to use a subject because that forces you to mess with the internals of your observable and handle your own errors, I would prefer an out of the box solution. – Josh Pittman Oct 14 '19 at 07:49
0

I'm making a couple of assumptions, hoping to present a more generic solution:

  • there's nothing special about a MouseEvent. We're trying to have an Observable of a certain type, owned by a component, and a way to update it.
  • using the Observable is a concern that should be separate from creating it, so it can be passed on to other components, combined with other observables, etc.
  • using refs to attach functionality to buttons is both confusing (since highly non-standard) and not very flexible (what happens if you want to pass that handler somewhere else?)

I'm combining two things in the snippet:

  • a createObservableWithNext function I described in another answer. It returns an Observable, and a next function to update it.
  • useObservable from the wonderful react-use package. Basically handles subscribing and unsubscribing to observables.
const FancyButton = () => {
  const { observable, next } = React.useRef(
    createObservableWithNext<MouseEvent<HTMLButtonElement>>()
  ).current;

  const latestClick = useObservable(observable);

  React.useEffect(() => {
    if (latestClick) {
      console.log(`clicked at ${latestClick.pageX} ${latestClick.pageY}`);
    }
  }, [latestClick]);

  return (
      <button onClick={next}>
        Next click
      </button>
  )
};
panepeter
  • 3,224
  • 1
  • 29
  • 41