DEV Community

loading...
Cover image for RxJS pipe as a React hook

RxJS pipe as a React hook

Kostia Palchyk
Hi! I write about code and nocode. JS, RxJS, React and other stuff. ❀️
・3 min read

Ever tried to use an Rx Observable in React? Then you know what's the problem with this code:

function App() {
  let [time, setTime] = useState(0);
  timer(0, 1000)
    .pipe(
      filter(x => x % 2),
      map(x => x + '!')
    )
    .subscribe(setTime);

  return <h1>{ time }</h1>
}
Enter fullscreen mode Exit fullscreen mode

Yeah, it subscribes to the timer with each render. Which triggers setTime on each timer emission. Which leads to a re-render. Which leads to... well, you know, memory leaks and weird behaviour. And on top of that it even won't be destroyed with the component unmount.

In this short post I want to share with you a non-canonical idea (probably not original one) how to fix that!

tl;dr: online playground with hook pipe

the hook

πŸͺ The hook

We could devise a custom react hook, that will fix that. Lets use a useEffect hook, which will subscribe to the source, and push messages to our observer (setTime in the example above)

let useObservable = (observable, observer) => {
  // useEffect with empty deps will call this only once
  useEffect(() => {
    let sub = observable.subscribe(observer); // connect
    return () => sub.unsubscribe(); // < unsub on unmount
  }, []);
}
Enter fullscreen mode Exit fullscreen mode

And it will be used like this:

function App() {
  let [time, setTime] = useState(0);
  useObservable(
    timer(0, 1000)
      .pipe(
        filter(x => x % 2),
        map(x => x + '!')
      ),
    setTime
  );

  return <h1>{ time }</h1>
}
Enter fullscreen mode Exit fullscreen mode

Which looks react-ish... but not rx-ish.
Not nice πŸ˜•. We can do better!

So let's explore another way!

πŸ—žοΈ RxJS pipes

But before we continue, a quick reminder of RxJS pipe operator mechanics:

Roughly speaking RxJS pipe operator (like, map) is just a function that takes one Observable and returns a new Observable:

(source: Observable<A>) => Observable<B>
Enter fullscreen mode Exit fullscreen mode

So when we subscribe to the resulting Observable<B>, operator subscribes to the source Observable<A>. And when that source emits a value, operator applies its logic to it (map, filter, etc) and decides what, when, and how to push to the resulting Observable<B>. map will push modified values, filter will push only values that satisfy given condition.

Okay, back to hooks

πŸͺπŸ—žοΈ The hook pipe

We can modify the hook to implement the Rx Operator interface, while still enclosing a useEffect hook.

Let's start with how we'll use it in a component:

function App() {
  let [time, setTime] = useState(0);

  timer(0, 1000)
    .pipe(
      filter(x => x % 2),
      map(x => x + '!'),
      useUntilUnmount()
    )
    .subscribe(setTime);

  return <h1>{ time }</h1>
}
Enter fullscreen mode Exit fullscreen mode

And here's it's implementation:

function useUntilUnmount() {
  // Observable => Observable interface
  return source => new Observable(observer => {
    // create a new Subscription
    // we'll use it to handle un-mounts and unsubscriptions
    let sub = new Subscription();

    // this is run only once
    useEffect(() => {
      // connect observer to source
      sub.add(source.subscribe(observer));
      // on unmount -- destroy this subscription
      return () => sub.unsubscribe();
    }, []);

    // return sub to handle un-subscriptions
    return sub;
  });
}
Enter fullscreen mode Exit fullscreen mode

This is really just 8 lines of code.

Disclaimer: while being leak-free and working as promised, this might not be the best way to use Observables in React. Tried <$> fragment already?

πŸ›ΈπŸ’¨ Outro

Try our shiny hook pipe (with dependencies!) in this online playground and leave a comment here with your opinion!

And in the future, when pipeline operator |> lands in JS, we'll probably substitute the subscribe with our custom hook subscribe. Like this:

function App() {
  let [time, setTime] = useState(0);

  timer(0, 1000)
    |> filter(x => x % 2)
    |> map(x => x + '!')
    |> subscribeHook(setTime)

  return <h1>{ time }</h1>
}
Enter fullscreen mode Exit fullscreen mode

That's it for today! Follow me here and on twitter for more RxJS, React, and JS posts!

I hope you had fun! If you enjoyed reading β€” please, indicate that with ❀️ πŸ¦„ πŸ“˜ buttons β€” it helps a lot!

Thank you for reading this article! Stay reactive and have a nice day πŸ™‚

Cya! πŸ‘‹

Psst.. Check out my other Rx / React articles!

πŸ˜‰

header image by Victor Garcia on Unsplash, gif taken from giphy.com

Discussion (2)

Collapse
rubenarushanyan profile image
Collapse
kosich profile image
Forem Open with the Forem app