Last active
March 30, 2021 09:01
-
-
Save pavanprakash21/21a604c3261f6494198caa8d90b3f838 to your computer and use it in GitHub Desktop.
React + Backbone Integration
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
componentWillReceiveProps// A basic Dumb component which only renders using props. If you want to embed | |
// this kind of component into a backbone view, look into DumbComponent view | |
// that extends the backbone view | |
import React from 'react'; | |
export const DumbComponent = ({ value }) => { | |
return <h1>{value}</h1> | |
} |
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
// It is important to return this after render so | |
// that backbone still has the context. | |
// In order to clean ourselvs up, it is not enough to call | |
// `unmountComponentAtNode` from the react dom because we | |
// also have to clean up the backbone side of things as well | |
// Embedding a react component in a backbone view | |
const DumbComponentView = Backbone.View.extend({ | |
render() { | |
const text = this.model.get('text'); | |
ReactDOM.render(<DumbComponent value={text} />, this.el); | |
return this; | |
}, | |
remove() { | |
ReactDOM.unmountComponentAtNode(this.el); | |
Backbone.View.prototype.remove.call(this); | |
} | |
}); |
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
// In a react class component, you have access to this.forceUpdate(), | |
// but since we want to deal everything in a functional way, | |
// we can use the following hook to do the same job for us. | |
//create your forceUpdate hook | |
import React, { useState } from 'react'; | |
export const useForceUpdate = () => { | |
const [value, setValue] = useState(0); // integer state | |
return () => setValue(value => ++value); // update the state to force render | |
}; |
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
// Listen to the changes on a backbone model | |
// Every time the model changes, useForceUpdate | |
// forces a rerender causing the react component | |
// to stay upto date with the data. | |
import React, { useEffect } from 'react'; | |
import { useForceUpdate } from './useForceUpdate'; | |
export const SingleItem = (props) => { | |
const { model } = props; | |
// Important to cache the hook to not cause an extra render. | |
// Also, memoize the whole function just to be safe. | |
const forceRerender = () => useForceUpdate(); | |
useEffect(() => { | |
model.on('change', forceRerender); | |
return function cleanup() { | |
model.off('change', forceRerender); | |
} | |
}); | |
return ( | |
<li>{this.props.model.get('text')}</li> | |
); | |
}; |
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
// Listen to the changes on a backbone collection | |
// Every time the collection changes, useForceUpdate | |
// forces a rerender causing the react component | |
// to stay upto date with the data | |
import React, { useEffect } from 'react'; | |
import { useForceUpdate } from './useForceUpdate'; | |
import { SingleItem } from './SingleItem'; | |
export const MultiItemList = (props) => { | |
const { collection } = props; | |
// Important to cache the hook to not cause an extra render. | |
// Also, memoize the whole function just to be safe. | |
const forceRerender = () => useForceUpdate(); | |
useEffect(() => { | |
model.on('add', 'remove', forceRerender); | |
return function cleanup() { | |
model.off('add', 'remove', forceRerender); | |
} | |
}); | |
return ( | |
<ul> | |
{collection.map(model => ( | |
<SingleItem key={model.cid} model={model} /> | |
))} | |
</ul> | |
); | |
} |
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
// We use a HOC that wraps the stateful react component | |
// to have upto date while also providing a way to pass | |
// the data up as well. | |
import React, { useRef, useEffect, useState } from 'react'; | |
export const connectToBackboneModel = (WrappedComponent) => { | |
const BackboneComponent = (props) => { | |
const prevModel = useRef(props.model); | |
const [modelState, setModelState] = useState(prevModel.attributes) | |
useEffect(() => { | |
setModelState(props.model.attributes()); | |
prevModel.off('change', handleChange); | |
props.model.on('change', handleChange); | |
prevModel.current = props.model; | |
return function cleanup() { | |
props.model.off('change', handleChange); | |
} | |
}, [props.model, prevModel]); | |
handleChange = (model) => { | |
setModelState(model.changedAttributes()); | |
} | |
let propsExceptModel = {...props}; | |
delete propsExceptModel.model; | |
return <WrappedComponent {...propsExceptModel} {...modelState} />; | |
} | |
return BackboneComponent; | |
}; |
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
// Consider a component that has to react on change, | |
// this can be purely react that knows nothing about the | |
// state/data management. | |
import React from 'react'; | |
export const Input = ({ text }) => { | |
return ( | |
<p> | |
<input value={text} onChange={props.handleChange} /> | |
<br /> | |
My text is {text}. | |
</p> | |
); | |
} |
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
// If we wrap that simple component with the HOC we created earlier, | |
// state change is easily propogated to the top where backbone can deal | |
// with that data. | |
// HOC that connects backbone to react. Input is the wrapped component. | |
export const BackboneNameInput = connectToBackboneModel(Input); |
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
// Now we can use that component anywhere we want | |
// while providing a mechanism for the child to handle | |
// the data. | |
// Final component which sets all the proper params | |
import React from 'react'; | |
export const FinalComponent = ({ model }) => { | |
function handleChange(e) { | |
model.set('some value', e.target.value); | |
} | |
return ( | |
<BackboneInput model={model} handleChange={handleChange} /> | |
); | |
} |
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
// Finally render by providing the backbone model | |
// as a prop to the final component. | |
// I know this is a lot of abstraction for something | |
// simple, but we have to consider 2 things. | |
// 1. If react is unaware of how state management is | |
// being done, it will be easier to integrate a state | |
// management solution like redux later. | |
// 2. Gives the advantage of 2-pass refactoring where | |
// you don't have to care about state management at the | |
// start of the refactor. You can complete the view refactor | |
// first and then you can worry about the state management. | |
import { ReactDOM } from 'react-dom'; | |
const model = new Backbone.Model({ firstName: 'Frodo' }); | |
ReactDOM.render( | |
<FinalComponent model={model} />, | |
document.getElementById('root') | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment