Skip to content

Instantly share code, notes, and snippets.

@steve-taylor
Created September 1, 2017 05:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steve-taylor/508dba438d65eeaa99686959a0b4b264 to your computer and use it in GitHub Desktop.
Save steve-taylor/508dba438d65eeaa99686959a0b4b264 to your computer and use it in GitHub Desktop.
Example of connecting a React component to Bacon streams and buses
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>bacon.js and baconjs-router POC</title>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.7.95/Bacon.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel" data-presets="es2015,react">
/**
* Create a factory of higher order components that render the specified inner
* component using the specified mapping of property names to the streams that
* feed them values and the specified mapping of callback property names to
* the buses onto which the callbacks' first parameter is pushed when called.
*
* This is similar in concept to react-redux's connect() function, but for
* Bacon.js instead of Redux, and without the bells and whistles.
*
* @param {object} mapStreamsToProps - mapping of property names to streams
* @param {object} mapBusesToProps - mapping of property names to buses
* @param {string} [name] - an optional display name to provide the component
* @returns {function} the connected component
*/
const connect = (mapStreamsToProps, mapBusesToProps, name) => Component => {
class Mapper extends React.PureComponent {
constructor(props) {
super(props);
this.openValve = new Bacon.Bus();
this.closeValve = new Bacon.Bus();
// Valve to buffer events while this component isn't mounted.
this.valveClosed = Bacon.update(
true,
[this.openValve], () => false,
[this.closeValve], () => true,
);
// Subscribe to input streams, mapping their values to the specified props
// via this component's state.
this.subscribers = Object
.keys(mapStreamsToProps || {})
.map(prop => mapStreamsToProps[prop].holdWhen(this.valveClosed).onValue(value => {
this.setState({[prop]: value});
}));
// Map callback props to bus pushes via this component's state.
this.state = Object
.keys(mapBusesToProps || {})
.reduce((buses, prop) => Object.assign(buses, {
[prop]: mapBusesToProps[prop].push.bind(mapBusesToProps[prop]),
}), {});
}
componentDidMount() {
this.openValve.push();
}
componentWillUnmount() {
this.closeValve.push();
// Unsubscribe from streams
this.subscribers.forEach(subscriber => {
subscriber();
});
}
render() {
return (
<Component
{...this.props} // Directly provided props
{...this.state} // Stream and bus mappings
/>
);
}
}
Mapper.displayName = name || `connect(${Component.displayName})`;
return Mapper;
};
</script>
<script type="text/babel" data-presets="es2015,react">
const MyComponent = ({foo, bar, onChangeFoo, onChangeBar, onSubmit}) => (
<div>
<div>
<label>
foo: <input type="text" value={foo} onChange={e => onChangeFoo(e.target.value)}/>
</label>
</div>
<div>
<label>
bar: <input type="text" value={bar} onChange={e => onChangeBar(e.target.value)}/>
</label>
</div>
<div>
<button onClick={onSubmit}>
Submit
</button>
</div>
</div>
);
const fooBus = new Bacon.Bus();
const barBus = new Bacon.Bus();
const submitBus = new Bacon.Bus();
const fooStream = fooBus.map(foo => foo.toUpperCase());
const barStream = barBus.map(bar => bar.toLowerCase());
Bacon.combineTemplate({
foo: fooStream,
bar: barStream
}).combine(submitBus, value => value).onValue(({foo, bar}) => {
alert(JSON.stringify({foo, bar}, null, 2));
});
// Push initial state
setTimeout(() => {
fooBus.push('foo');
barBus.push('bar');
});
// Connect MyComponent to streams and buses
const MyComponentConnected = connect({
foo: fooStream,
bar: barStream,
}, {
onChangeFoo: fooBus,
onChangeBar: barBus,
onSubmit: submitBus
})(MyComponent);
ReactDOM.render(
<div>
<header>
Header
</header>
<MyComponentConnected/>
<footer>
Footer
</footer>
</div>,
document.querySelector('#app')
);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment