Skip to content

Instantly share code, notes, and snippets.

@pavanprakash21
Last active March 30, 2021 09:01
Show Gist options
  • Save pavanprakash21/21a604c3261f6494198caa8d90b3f838 to your computer and use it in GitHub Desktop.
Save pavanprakash21/21a604c3261f6494198caa8d90b3f838 to your computer and use it in GitHub Desktop.
React + Backbone Integration
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>
}
// 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);
}
});
// 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
};
// 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>
);
};
// 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>
);
}
// 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;
};
// 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>
);
}
// 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);
// 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} />
);
}
// 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