A tool for migrating from Backbone stack to react+redux without disturbing the project or putting it on hold for a rewrite.
Lets Backbone and Redux apps coexist, so you can write new features in redux and continue with regular releases while a rewrite is progressing.
Initial state: You have a Backbone or Backbone+Marionette app to migrate away from
- Learn how redux works, at least a tiny bit (embrace a world where inheritance and event emitters are not the way)
- Create a redux app and embed it in your old stack
- Rewrite one view to redux
- use as many components as you want in place of one complex view
- use one-way data flow in redux and fetch the data separately, forget Backbone models
- use migrator's simple view generator to plug your redux app in Backbone and point to the right component
- Delete old view, delete its model if nothing else uses it
- If any Backbone views left, goto 2
- With all the app rewritten, remove Backbone routing and layout. Replace
Chooser
with router of your choice
Final state: All features rewritten to redux, project is still alive, people stopped avoiding your job offers.
One more tip: You don't have to rewrite your whole single page app, just split it into more apps. People will accept a page load when going from main application to settings.
npm install backbone-redux-migrator
backbone-redux-migrator requires react
and react-redux
installed in the project. It's not listing them as dependencies in package.json, so you don't end up having duplicated dependencies when you use a different version of react or old version of npm
.
require("backbone-redux-migrator")
: migrator
factory function that wraps your redux app
migrator
instance API exposes functionalities to Backbone:
getView
- returns a View class for displaying redux app within Backbone layoutgetViewMarionetteCompat
- returns a View class that uses Marionette'sonRender
andonDestroy
instead ofrender
. Use it only if getView breaks your complicated Marionette view logic.getModelReadonly
- returns a Model class for reading storedispatchAction
- dispatches a redux action
"backbone-redux-migrator/Chooser" exports Chooser
, ConnectedChooser
, Choice
components
In your main redux app entrypoint reduxMain.js
/* all the redux dependencies would also be here */
import {ConnectedChooser, Choice} from "backbone-redux-migrator/Chooser"
import migrator from "backbone-redux-migrator"
//I bet you have a nicer way to pass it to Backbone app, but I didn't want to add a build system in readme
window.Backbone.reduxApp = migrator({/*options*/}, function(renderRoot, choiceReducer){
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
store = createStoreWithMiddleware(combineReducers({
/* your reducers here */
choice: choiceReducer
}));
ReactDOM.render(
<Provider store={ store }>
<ConnectedChooser>
<Choice name="home">
<MyHomeComponent />
</Choice>
<Choice name="item">
<MyItemComponent />
</Choice>
</ConnectedChooser>
</Provider>,
renderRoot
);
return store; //This is very important!
})
Now in Backbone/Marionette app just get a view and use it without a model
var reduxHomeView = Backbone.reduxApp.getView("home")
var reduxHomeView = Backbone.reduxApp.getViewMarionetteCompat("home", CustomView)
//IF inheriting from a complex Marionette view that didn't work for you with getView
var reduxHomeViewMarionetteCompatible = Backbone.reduxApp.getViewMarionetteCompat("home", Backbone.Marionette.ItemView)
How do I render a parametrized item view if I can only choose components by name? The redux way - by setting it in store using an action.
Backbone.reduxApp.dispatchAction({ type: SET_ITEM, item: itemId })
ConnectedChooser
is using connect
from react-redux
to automatically retrieve the choice from your store. If you decide to use a different key in your store instead of choice
you can import Chooser
and wrap it in connect
on your own. You can also use Chooser
directly. It requires you to pass chosen
property.
Readonly access to store form Backbone app
Sometimes, when you rewrite a functionality to redux side it turns out that something else depended on reading a field from your functionality's model. You can provide a model based on redux store contents, which will update from store on every fetch()
call.
The model calls fetch()
in initialize
, so it should be ready to use without calling fetch()
manually.
var HomeTilesModel = Backbone.reduxApp.getModelReadonly(function mapStateToModel(state){
return state.home.tiles
})
var customModel = Backbone.reduxApp.getModelReadonly(function mapStateToModel(state){ /* ... */ }, CustomModelClass)
Let me know if you're also migrating!
Questions You Might Have
That would require forcing consumers of this library to use react-router (or any other router I integrate with, but not their choice). Letting Backbone app dispatch actions seems like a good replacement and is much more versatile. Any parametrized route can be a field in store instead.
With the right reducers in place, one can even push content of a fetched model into the redux app to save some http requests using dispatchAction
. But this is only recommended if you're in the process of rewriting multiple views to react+redux and some still need the model.
See paragraph above :)
When they were first created, react mixins/bridges for Backbone were very promising. They enabled a lot of good rewrites of ugly views to reusable Rect components, but they didn't eliminate the complexity of 2way bindings. Many projects found them to be a dead-end, with all the component lifecycle functions to connect to models and mistakes in event handling causing bursts of re-renders.
This migrator package is aiming at letting you build a new redux app inside existing codebase, but in total isolation in terms of data flows. Backbone keeps the routing (and probably main layout) while your redux rewrite is growing.
Also, backbone-redux-migrator is sooo much simpler.
window.location
updates to change route.
You can use store based model to get data that redux app fetched from the server.
Hopefully you won't need anything else.
I didn't find a way to communicate back that wouldn't introduce unwanted dependencies or "magic" into your nice new redux app. But if I figure it out, I'll let you know.
It could, but the aim of this library is to help migrating away from old codebase, so all features here are focused on making sure redux app can be easily separated and doesn't depend on any behavior logic from Backbone side except routing.
Are you aware that store based model + dispatchAction is enough to try to use redux from Backbone and only implement reducers?
Yes, but if that was a good idea, I would have implemented it in the store based model.
I'm sure if you try, it can get ugly really fast. Please don't do it. :) Feel free to get in touch for suggestions on how to avoid this.
- add a step-by-step migration example
- add helpful warnings
- continue to look for ways to let redux app use a router of any kind
write tests to encourage collaborationadd a way to communicate from redux to Backbone with which it's not easy to shoot yourself in a foot.with store based model it should be enough.