use-stream

Rx streams as a communication bus for React

Downloads
9
Stars
2

This an exploration of an idea I've had for a while. While we've found that the built-in React hooks (useState, useReducer) are more than complete enough to replace a state management solution like redux, there is more to the picture.

redux is used as an event bus as often as it is used for state management. Libraries like redux-observable and redux-sagas are examples of this pattern in the wild. For me, this is valuable functionality and is missing in our out-of-the-box React setup.

So this is my attempt to bring it back! This is a set of hooks that can create an event bus (via observables) that is stored in context. The event bus can be subscribed to and emitted to throughout the React application tree. Importantly, this is a separate stream to the actions that update our state which allows for better separation of concerns.

Installation

yarn add @twopm/use-stream

Setup

First, define the types of the events that will be sent along the bus.

export type MouseClicked = { type: 'mouseClicked' }
export type SpacePressed = { type: 'spacePressed' }
export type MouseMoved = { type: 'mouseMoved'; location: [number, number] }

export type Events = MouseClicked | SpacePressed | MouseMoved

Second, create the bus itself and the hooks to use it.

import { makeEventStream, makeEventStreamContext } from '@twopm/use-stream'

export const stream = makeEventStream<Events>('main')
export const EventStreamContext = makeEventStreamContext<Events>()

export const hooks = createHooks(EventStreamContext)

Then finally, add the Provider to your app.

import { EventStreamContext } from './streamConfig'

const App = () => {
  return (
    <EventStreamContext.Provider value={stream}>
      {/* my phenomenal app goes here */}
    </EventStreamContext.Provider>
  )
}

Subscribing

import { useStreamCallback } from '@twopm/use-stream'
import { EventStreamContext, hooks } from './streamConfig'
import { filter } from 'rxjs/operators'

export const ClickTracker = () => {
  const [clicks, setClicks] = useState(0)
  
  hooks.useSubscribe(
    s =>
      s
        .pipe(
          filter(x => x.type === 'mouseClicked')
        )
        .subscribe(_ => {
          setClicks(clicks + 1)
        }),
    [clicks, setClicks]
  )
  
  return <div>Clicked: {clicks} times</div>
}

Emitting

import { EventStreamContext, hooks } from './streamConfig'

export const ClickEmitter = () => {
  const emit = hooks.useEmit()
  const onClick = () => emit({ type: 'mouseClicked' })

  return <button onClick={onClick}>Click Me!</button>
}

Running this repo

Bootstrap

yarn
yarn bootstrap

Running the examples

cd packages/use-stream
yarn build
cd ../example
yarn start