Created
September 1, 2017 05:29
-
-
Save steve-taylor/508dba438d65eeaa99686959a0b4b264 to your computer and use it in GitHub Desktop.
Example of connecting a React component to Bacon streams and buses
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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