Search code examples
react-nativerxjsevent-handlingreact-native-textinput

How to create an rxjs Observable from TextInput (either onChange or onTextChange)


I want to create an observable from a change event that gets fired on a React Native TextInput component. TextInput comes with 2 change props that I'm aware of (onChangeText and onChange). From what I gather, you need to use onChange if you want access to the native event you need to use onChange.

I don't know much about the native event object. I am trying to create an rxjs observable using fromEvent.

First I created a ref in my functional component like this:

const sqftRef = useRef().current

Then I attached this ref to the TextInput component like this:

 <TextInput
    ref={sqftRef} // attach a ref 
    label='Sqft'
    mode='flat'
    textContentType='none'
    autoCapitalize='none'
    keyboardType='numeric'
    autoCorrect={false}
    value={String(formValues.sqft)}
    dense
    underlineColor={colors.colorOffWhite}
    onChangeText={(text) => setText(text)}
    onChange={e => {
        // somehow create an observable from this event ???
    }}
    style={styles.inputStyles}
    theme={inputTheme}
 />

I tried to create an Observable using fromEvent like this but it doesn't work. I get undefined is not an object (evaluating target.addEventListener):

fromEvent(sqftRef, 'onChange').subscribe(value => console.log(value))

I know my approach is all wrong. Hoping someone can point me in the correct direction.


Solution

  • OK, I was able to figure it out with the help of Amer Yousuf and Alex Fallenstedt.

    I did something similar to what Alex suggested, modifying his solution for React Native. One reason his solution wasn't working for me is that it is important to use the useRef hook to prevent the Observable from being re-created on each render. If the observable is recreated (on a re-render) and useEffect doesn't run again, then we won't have an active subscription to the newly (re-created) observable (useEffect never runs again). That's why my call to sqft$.next was originally only being called once (the first time until we re-render).

    My solution looks like this:

    let sqft$ = useRef(new BehaviorSubject(0)).current
    
    useEffect(() => {
            const sub = sqft$.subscribe({
                next: (val) => {
                    // just testing stuff out here
                    updateForm('sqft', val)
                    updateForm('lot', val * 2)
                }
            })
          // this is only relevant to my use case
          if (activeReport) sqft$.next(activeReport.sqft)
          return () => sub.unsubscribe()
    
       }, [activeReport])
    

    and of course I call this in onChangeText:

    onChangeText={(text) => {
       sqft$.next(text)
    }}
    

    So this is working right now. I still feel like there may be a better way using onChange(e => ...stuff). I will leave this question open for a little bit in case anyone can break down how to do this using nativeEvent or explain to me how I can access an event off the TextInput component.