Last active
January 30, 2020 00:48
-
-
Save markerikson/fe53bf4c4eb3a040a0007c16e043b322 to your computer and use it in GitHub Desktop.
Backbone/React Interop
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
import {viewFromComponent} from "viewUtilities"; | |
class MyListComponent extends React.Component { | |
render() { | |
// serialized Backbone collection | |
const {items = [] } this.props; | |
// do stuff with items | |
} | |
} | |
class MySingleComponent extends React.component { | |
render() { | |
// serialized Backbone model, with whatever your real fields are | |
const {a, b, c} = this.props; | |
// do stuff with fields | |
} | |
} | |
const MyListView = viewFromComponent(MyListComponent, { | |
observe : { | |
// Every time one of these events is triggered, the collection will be serialized | |
// and the HOC will call this.setState() | |
collection : "add remove reset", | |
}, | |
actions : { | |
addNewItem(newItem) { | |
// This function runs in the context of the HOC generated by viewFromComponent, | |
// so `this` is actually the parent of your "real" component. This gives you | |
// access to the model/collection instance to let you mutate them, while keeping | |
// your real component unaware of Backbone entirely. | |
// Yes, this is sorta-kinda mutating "props" in a way, but it's an incremental | |
// step towards turning this into a component callback or Redux action creator. | |
this.props.collection.add(newItem); | |
} | |
} | |
}); | |
const MySingleView = viewFromComponent(MySingleComponent, { | |
observe : { | |
model : "change" | |
}, | |
actions : { | |
setSomeField(newValue) { | |
this.props.model.set("someField", someValue); | |
} | |
} | |
}, {staticClassProp : 123}, {id : "myViewId", el : "#divToAttachTo"}); | |
const collectionView = new MyListView({collection : someBackboneCollection}); | |
const modelView = new MySingleView({model : someBackboneModel}); |
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
// See https://github.com/Workable/backbone.react-bridge for the original implementation. | |
// I've customized this one in several ways. | |
const defaultObserveEvents = { | |
model: "change", | |
collection: "add remove reset", | |
}; | |
function defaultGetProps({model, collection, state = {}} = {}) { | |
if (model) { | |
Object.assign(state, model.toJSON({computed: true})); | |
} | |
if (collection) { | |
Object.assign(state, {items: collection.toJSON({computed: true})}); | |
} | |
return state; | |
} | |
function extractNewState(options = {}) { | |
const {getProps = defaultGetProps, props} = options; | |
const newState = { | |
...getProps(options), | |
...props, | |
}; | |
return newState; | |
} | |
class BackboneContainer extends React.Component { | |
static defaultProps = { | |
observe: {}, | |
actions: {}, | |
}; | |
constructor(props) { | |
super(); | |
this.state = extractNewState(props); | |
const {actions = {}} = props; | |
this.actions = _.mapValues(actions, action => action.bind(this)); | |
} | |
componentDidMount() { | |
const {model, collection, observe} = this.props; | |
const potentialEventsToSubscribeTo = {...defaultObserveEvents, ...observe}; | |
this.eventsToSubscribeTo = Object.keys(potentialEventsToSubscribeTo).reduce((obj, key) => { | |
const backboneItem = this.props[key]; | |
if (backboneItem) { | |
const events = potentialEventsToSubscribeTo[key]; | |
obj[key] = {backboneItem, events}; | |
} | |
return obj; | |
}, {}); | |
_.forEach(this.eventsToSubscribeTo, entry => { | |
const {backboneItem, events} = entry; | |
this.initListener(backboneItem, events); | |
}); | |
} | |
componentWillUnmount() { | |
_.forEach(this.eventsToSubscribeTo, entry => { | |
const {backboneItem, events} = entry; | |
this.destroyListener(backboneItem, events); | |
}); | |
} | |
initListener = (entity, events) => { | |
if (!entity) { | |
return; | |
} | |
if (events instanceof Array) { | |
events = events.join(" "); | |
} | |
entity.on(events, this.updateComponentState); | |
}; | |
updateComponentState = () => { | |
const newState = extractNewState(this.props); | |
this.setState(newState); | |
}; | |
destroyListener = (entity, events) => { | |
if (!entity) { | |
return; | |
} | |
entity.off(null, this.updateComponentState); | |
}; | |
render() { | |
const {actions = {}} = this; | |
const { | |
actions: propActions = {}, | |
observe, | |
model, | |
collection, | |
children, | |
render = children, | |
...otherProps | |
} = this.props; | |
const childProps = {...actions, ...this.state, ...otherProps}; | |
return renderProps(render, childProps); | |
} | |
} | |
function viewFromComponent(Component, options = {}, classOptions = {}, wrapperOptions = {}) { | |
const {id, el, className = ""} = wrapperOptions; | |
const ReactMarionetteView = Backbone.Marionette.ItemView.extend( | |
{ | |
id, | |
el, | |
template: _.template(""), | |
className: "react-marionette-view " + className, | |
onRender() { | |
if (this._reactInternalInstance) { | |
return false; | |
} | |
// Create and render a container component wrapping the given component | |
//const props = Object.assign({observe : {}, actions : {}}, options, this.options); | |
const props = { | |
...options, | |
...this.options, | |
render: Component, | |
}; | |
this._reactInternalInstance = ReactDOM.render(<BackboneContainer {...props} />, this.el); | |
}, | |
onDestroy() { | |
this._reactInternalInstance._isMounted = false; | |
ReactDOM.unmountComponentAtNode(this.el); | |
}, | |
onClose() { | |
this._reactInternalInstance._isMounted = false; | |
ReactDOM.unmountComponentAtNode(this.el); | |
}, | |
}, | |
classOptions, | |
); | |
return ReactMarionetteView; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you so much Mark!