Forem

Cover image for RxJS pipe as a React hook
Kostia Palchyk
Kostia Palchyk

Posted on

RxJS pipe as a React hook

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

Image of Timescale

πŸš€ pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applicationsβ€”without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more β†’

Top comments (2)

Collapse
 
rubenarushanyan profile image
Ruben Arushanyan β€’
Collapse
 
kosich profile image
Kostia Palchyk β€’

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

πŸ‘‹ Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay