TLDR;

You don’t have to be a javascript ninja to start using streams with React. The article shows a few examples demonstrating how and the component that makes it all so simple.

Streams are awesome. Everything that happens on client-side can be reduced into a stream: a stream of DOM updates, a stream of redux state changes, a stream of scrolling events, etc.

It implies that mastering streams skill is a crucial part of modern client-side engineering.

Luckily, there is a very simple way of how to use/render streams with React. And by rendering a stream I mean converting data from a stream into something visual. It can be a value like a date/time or behavior of elements on a page.

Few Examples

Let’s start with a few examples, if you are short on time, then scroll to the end of the article where I’ll describe how to actually render a stream, it is pretty simple, I promise!

For streams, I’ll be using awesome Kefir library. Highly recommend it, but it is up to you which library you would prefer, it does not really matter.

Date/Time

Let’s render the date/time value that updates every second. If you ever tried that, you might remember how bulky such a simple component might look like, but with a stream, it is very simple:

import React from 'react';
import Kefir from 'kefir';
import FromStream from './FromStream';

const dateStream = Kefir.interval(1000).map(() => new Date().toString());

export default () => (
  <FromStream stream={dateStream}>
    {(dateString) => dateString}
  </FromStream>
);

Scroll

A bit more complicated example would be a component that renders a custom scroll bar.

import React from 'react';
import Kefir from 'kefir';
import FromStream from './FromStream';

const scrolledPercentStream = Kefir.fromEvents(document, 'scroll').map((e) => {
  const scrollY = window.document.pageYoffset;
  const scrollHeight = e.target.body.scrollHeight;
  return scrollY / (scrollHeight - windiw.innerHeight) * 100;
});

export default () => (
  <FromStream stream={scrolledPercentStream}>
    {(percent) => (
      <div className="bar" style={{ top: `${percent}%` }}></div>
    )}
  </FromStream>
);

Again, super simple. Here we have a simple scroll stream that mapped into a scrolled percentage stream, which then used to set top value for scroll bar cursor styles.

Animations

If you ever used a library like react-transition-group, then the code would probably look familiar to you. But the thing I like the most is that we don’t have to load additional dependency for animations, streams library and a little bit code is all we need:

import React, { PureComponent } from 'react';
import FromStream from './FromStream';

const ENTERING = 'ENTERING';
const ENTERED = 'ENTERED';
const EXITING = 'EXITING';
const EXITED = 'EXITED';

const inStates = [ENTERING, ENTERED];
const outStates = [EXITING, EXITED];

const createAnimationStream = (events, timeout) => {
  return Kefir.stream((emitter) => {
    emitter.emit(events.shift());
    const interval = setTimeout(() => {
      const nextEvent = events.shift();
      if (!nextEvent) {
        emitter.end();
      } else {
        emitter.emit(nextEvent);
      }
    }, timeout);

    return () => {
      return clearInterval(interval);
    };
  }).toProperty();
};

export default class Animate extends PureComponent {
  static defaultProps = {
    in: null,
    out: null,
    timeout: 100,
  }

  render() {
    if (this.props.in === null && this.props.out === null) {
      return this.props.children(ENTERED);
    }
    
    const events = this.props.in ? inStates : outStates;
    const animateStream = createAnimationStream(events, this.props.timeout);
    return (
      <FromStream stream={animateStream}>
        {this.props.children}
      </FromStream>
    );
  }
}

This is a bit longer than previous examples, but still about 20x shorter than the react-transition group lib. Of course, it has fewer features, but there is a lot of room for a few more lines of code.

FromStream component

So, let’s have a look at the FromStream component itself:

import { PureComponent } from 'react';

export default class FromStream extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { value: false };
  }

  componentDidMount() {
    this.initStream();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.stream !== this.props.stream) {
      this.initStream();
    }
  }

  componentWillUnmount() {
    if (this.unSubscribe) {
      this.unSubscribe();
    }
  }


  initStream() {
    if (this.unSubscribe) {
      this.unSubscribe();
      this.unSubscribe = null;
    }

    if (this.props.stream) {
      const onValue = (value) => {
        this.setState(() => ({ value: map(value) }));
      };

      this.props.stream.onValue(onValue);
      this.unSubscribe = () => stream.offValue(onValue);
    }
  }

  render() {
    return this.props.children(this.state && this.state.value);
  }
}

Nice and simple 42 lines of code. No magic. But gives a great power to work with streams in React app.

Examples above show that sometimes streams are very helpful and with mere React component they can be very handy for rendering events data, or even for complex cases like animations.

Of course, examples are simplified for clarity and in real-world, they would probably be a bit more complicated. Not so much, though. I’ve checked how we use streams in the Holloway reader app and found that the components we use are about at the same level of complexity as examples above.

If you feel like examples from the article are not enough detailed or explained, or you have some concerns about streams + React, or you have some other thoughts, feel free to reach me out, I would love to hear your feedback!

#reactjs

How to use/render streams with React
27.50 GEEK