Reputation: 7324
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'
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>
);
};
Upvotes: 7
Views: 6277
Reputation: 2172
The solution above using references is elegant but it wasn't working for me when trying to hook into the 'onChange' event on a MUIv5 TextField component. I ended up with a different solution that I actually prefer as it is more direct, understandable and robust in my opinion. First create a RxJS subject and then just push next events from the event handler. Note that you don't need to be using MUI components for this to work. For example:
import TextField from '@mui/material/TextField'
import { Subject } from 'rxjs'
import { useEffect } from 'react'
const textSubject = new Subject<string>('')
const FancyTextInput = () => {
useEffect(() => {
const sub = textSubject.subscribe({next: text => console.log(text)})
return () => sub.unsubscribe()
}, [])
return <TextField onChange={e => textSubject.next(e.target.value)}/>
}
Upvotes: 0
Reputation: 4242
I'm making a couple of assumptions, hoping to present a more generic solution:
MouseEvent
. We're trying to have an Observable
of a certain type, owned by a component, and a way to update it.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.ref
s 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:
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>
)
};
Upvotes: 0
Reputation: 11345
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>
);
};
Upvotes: 12