Rx streams as a communication bus for React
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.
yarn add @twopm/use-stream
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>
)
}
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>
}
import { EventStreamContext, hooks } from './streamConfig'
export const ClickEmitter = () => {
const emit = hooks.useEmit()
const onClick = () => emit({ type: 'mouseClicked' })
return <button onClick={onClick}>Click Me!</button>
}
yarn
yarn bootstrap
cd packages/use-stream
yarn build
cd ../example
yarn start