Functional State Management for React using RxJS
I've been developing React applications with Redux for a while now, and for about as long I've been thinking "I bet there is a simpler way to do this using RxJS." I'm about 70% finished building a redux replacement based on RxJS, and I'd like some opinions about it. It leverages the power of observables, which allows you to do some interesting things, and I think it simplifies things (you be the judge of that though). Here's what a sample app might look like.
events.js
import {
createEventHandler,
connectEventHandler
} from 'redurx';
export const submitHandler = createEventHandler();
export const inputChangeHandler = createEventHandler();
export const checkChangeHandler = createEventHandler();
export const connectToSubmit = connectEventHandler(
submitEvent.stream
.withLatestFrom(
inputChangeEvent.stream.map((e) => e.target.value),
checkChangeEvent.stream.map((e) => e.target.checked),
(e, string, boolean) => { string, boolean }
)
);
Here we're setting up handlers for different events in the application. Note that I can use the stream (which is just a BehaviorSubject
in Rx) on the handler and map, combine, and do whatever with the events themselves. An interesting thing to note here is that the results of the change events will update within the observable, and it isn't until a submit event is fired that the final observable emits a value.
state.js
import { createState } from 'redurx';
import { connectToSubmit } from './events';
const state = createState({
submits: 0,
values: {
string: '',
boolean: false
}
});
connectToSubmit(
state.child('values').hook((newValues) => newValues),
state.child('submits').hook((_, submits) => submits + 1)
);
export default state;
Here we're creating the state for our application. What create state basically does is walk your entire state tree and create a BehaviorSubject
for each child. We can get their latest value directly (which becomes important in the component), and we can hook them up to events with a next and error handlers, and return a new value for that child. You can hook into any point in the tree, whether it's the whole state, an object, or a single value.
component.js
import React from 'react';
import { connectToState } from 'redurx';
import state from './state';
import {
submitHandler,
inputChangeHandler,
checkChangeHandler
} from './events';
const Component = ({ string, boolean, submits }) => {
return (
<div>
<p>You've submitted {submits} times.</p>
<form onSubmit={submitHandler}>
<input type='text' defaultValue={string} onChange={inputChangeHandler} />
<input type='checkbox' defaultChecked={boolean} onChange={checkChangeHandler} />
<button type='submit'>Submit</button>
</form>
</div>
)
};
export default connectToState({
string: state.child('values').child('string'),
boolean: state.child('values').child('boolean'),
submits: state.child('submits')
})(Component);
In the component we import the state and the event handlers. We can map state to props pretty simply by using the connectToState
higher order component, which just gets the latest value from the different parts of the state and passes them as props to the wrapped component on render. We don't have to do that within a function, and instead we can do it right when we create the component. Also notice that we don't need to bind our event handlers, or have a provider anywhere. We also get "should component update" for free, since the component will only re-render if a distinct value comes down the pipe.
Like I said, it's 70% finished, with the remaining piece being the React integration. I'd like to hear some thoughts though as far as if it makes sense, or if there's any features you'd like to see. Thanks!