Wrapping a Marionette app in React

Working on a legacy JavaScript app powered by Backbone and Marionette, and willing to use React to develop new features? While that is most certainly feasible, integrating the former in the latter has its quirks, especially if there is routing involved on both sides.

Let's say your Marionette app looks something like the following:

import Marionette from "backbone.marionette";
import LayoutView from "./views/LayoutView";

export default Marionette.Application.extend({
// ...

onBeforeStart() {
this.layoutView = new LayoutView();
},

onStart() {
Backbone.history.start({
pushState: true
});
}
});

And LayoutView implicitly renders in a div#app element:

import { View } from "backbone.marionette";

export default View.extend({
el: "#app",

// ...
});

Chances are you will be willing to keep using this same element as the root of your brand new React app:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
import Wrapper from "./Wrapper";

ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<nav>
<Link to="/">Marionette app</Link>
<Link to="/new-route">New route</Link>
</nav>
<Switch>
<Route path="/new-route">
<div>Haha React go brrr</div>
</Route>
<Route path="/">
<Wrapper />
</Route>
</Switch>
</BrowserRouter>
</React.StrictMode>,
document.getElementById("app")
);

The Wrapper component will therefore be responsible for the initialization and encapsulation of the Marionette app:

import React, { useRef, useEffect } from "react";
import Backbone from "backbone";
import { useLocation } from "react-router-dom";
import MarionetteApp from "../backbone/app";

const app = new MarionetteApp();

export default function Wrapper() {
const wrapper = useRef(null);

useEffect(() => {
app.start({ el: wrapper.current });

// This will be called upon unmounting the component,
// i.e. navigating to a new, React-powered route
return () => {
app.destroy();
};
}, []);

// Ensure React links trigger Backbone navigation properly
useEffect(() => {
Backbone.history.navigate(location.pathname, { trigger: true });
}, [location.pathname]);

return (
<div ref={wrapper}></div>
);
}

Now, all that is left to do is to remove the el key from LayoutView, and modify the Marionette app accordingly:

import Marionette from "backbone.marionette";
import LayoutView from "./views/LayoutView";

export default Marionette.Application.extend({
// ...

onBeforeStart(app, options) {
this.layoutView = new LayoutView();

// Explicitly render inside the given node, i.e. our Wrapper component's
options.el.innerHTML = this.layout.render().$el;
},

// ...

onDestroy() {
this.layoutView.destroy();
Backbone.history.stop();
}
});

Implementing onDestroy as such ensures everything is cleaned up in order to start fresh when the user browses / again and your Marionette app is remounted; it will not work otherwise.

For this setup to be complete, you will also need to watch route changes on the Marionette side, in order to notify React Router accordingly. Your mileage may vary regarding how to do it; hopefully, you already are overriding Backbone.Router or Marionette.AppRouter and emitting an event you can subscribe to. Regardless, here is what you want to do in the Wrapper component:

// ...
import { useLocation, withRouter } from "react-router-dom";
// ...

function Wrapper() {
const wrapper = useRef(null);

useEffect(() => {
app.start({ el: wrapper.current });

// Propagate Backbone route changes to React
someBackboneRadioChannelOrWhatever.on("navigationEvent", () => {
history.push("/" + Backbone.history.fragment);
});

return () => {
someBackboneRadioChannelOrWhatever.off("navigationEvent");
app.destroy();
};
}, []);

// ...
}

export default withRouter(Wrapper);

Retrieving the last post title from Beamer

Upon integrating the Beamer widget in your application, you might have noticed a tooltip appearing over the HTML element you bound to it whenever a new post is published. This feature isn't mentioned in the documentation as far as I can tell, which also implies it cannot be disabled. However, there is a way to do so in JavaScript, and even use the last post title any other way you see fit, even though this information isn't provided to you initially!

The window.Beamer object exposes the ajax method, used by Beamer to request data from its backend: this method is used, among other things, to retrieve the last post title and display it in the aforementioned tooltip. One can use that to their own advantage, and override the method as follows:

let lastPostTitle;
const ajax = window.Beamer.ajax; // keep a reference to the original method...

window.Beamer.ajax = (url, callback) => {
ajax(url, response => { // ...and call it ourselves
try {
// Grab the info for later use if it is available
lastPostTitle = JSON.parse(response).lastPostTitle || "";
} catch (e) {} // fail silently otherwise

callback(response);
});
};

Finally, we can make sure the tooltip is never shown by replacing the method displaying it with a no-op function:

window.Beamer.showLastPostTitle = () => {};

Using expressions as effect dependencies in React functional components

The latest major version of React introduced what has become one of its major features, namely hooks, which enable one to benefit, among other things, from state and lifecycle mechanisms inside a functional component, that is to say one defined as a function rather than a class. The component's lifecycle is thus handled by the useEffect hook, which binds together the behaviours of componentDidMount, componentDidUpdate, and componentWillUnmount. It is used as such:

useEffect(() => {
// This will run upon mounting or unmounting the component,
// as well as everytime it rerenders
});

Of course, this is very rarely what you want: most of the time, you want the effect to run when given props or state values change, which is done as follows:

const [bar, setBar] = useState("");

useEffect(() => {
// This will run when the component rerenders
// because one of the values listed below changed
}, [props.foo, bar]);

You can also pass [] as the second argument to limit the effect's scope to mounting and unmounting of the component.

But you might also want to be nitpicking even more than that: in some situations, it could be ideal to have the effect run based on a computed value. Imagine a component A reading a value from a remote API, and supplying it to its child component B through props; B can then trigger a behaviour in A that would cause that value to change. It is a perfectly valid use case to want to process the first "change" (actually receiving the initial value with a delay) differently than the subsequent ones. Well, the following works just fine:

useEffect(() => {
// This will only run the first time props.foo receives a defined value
}, [props.foo !== undefined]);

This is a pretty helpful workaround to the fact we have no access to effect dependencies' previous values.

In my experience, if you find yourself writing a "complex" effect dependency such as this, half the time there is a simpler solution to your problem; but the other half, knowing this kind of thing is possible can come in handy (and it just makes sense: under the hood, React just matches the values against the previous ones to know whether it should run the effect again or not).

Last but not least, toying around with that dependency list is also useful if a value you depend on is an Object or an Array, since there is a fair chance those are not mutated when their values change, and these therefore cannot be matched against the previous ones:

useEffect(() => {
// This will only run if someArray's contents are actually different,
// even if the parent component overwrote it [thisWay, ...orSomething]
}, [JSON.stringify(someArray)]);

I would be interested to know if other people are using expressions in their effects' dependency lists, so you know what to do!

Handle circular NPM package dependencies with Webpack

If you maintain your own set of JavaScript libraries and publish them on NPM, you might find yourself in a situation where a package A depends on a package B, which in turn depends on A itself. Although you should generally strive to avoid such an ordeal, one can always imagine some edge case where this is a valid way of implementing things. The main problem is that it means each package will require a version of itself installed in node_modules which, on top of being semantically ugly, can make for really painful upgrades, as bumping the version of B in A and then releasing A should be followed by updating to this new version in B, and so forth... so what can we do to avoid this?

The quickest way to break the cycle is to remove A from B's dependencies (or the other way around, depending on what makes the most sense) and add it to its peerDependencies instead. It basically tells your package consumers that the latest compatible version of A should be installed alongside B for everything to work properly, without installing it directly: this transfers responsibility of maintaining the installed version of A onto them. You might also want to add A to B's devDependencies if its presence is necessary for tests to run properly, or anything else related to its maintenance. This should ease the pain within your dependency tree!

Finally, when developing A (which still explicitly depends on B), you might need to set things up so import calls targetting A resolve to your local code, the twin dependency not being around anymore. Doing so depends on your specific build setup, but it is pretty easy to do with Webpack thanks to the resolve part of its configuration:

{
resolve: {
// tell Webpack to look for third-party code in A's root directory before node_modules
modules: [__dirname, "node_modules"],

alias: {
"A": __dirname // tell it to resolve A to this same directory
}
},

// ...
}

This example assumes your entry point (typically index.js) lives in A's root directory; if it rather is is src/, for example, you will want to use path.resolve(__dirname, "src") instead.

Feel free to let me know of a better way to overcome such pitfalls when they cannot be avoided, if you know any!

Overcoming race conditions when using a global event bus in Vue.js

To ease communication between components which are not directly related, and when using a state manager such as Vuex seems overkill, Vue.js developers' go-to pattern is the global event bus: while it feels kind of hacky, it most certainly is easy to both use and reason about.

Or is it though? Depending on your specific use case, there is virtually no guarantee the event listeners will be bound before the events are emitted. Wouldn't it be nice if we had some sort of mechanism that could keep the early-emitted values in memory and apply them upon binding the listeners? Why do I keep asking questions when you know damn well the answer lies right below?

import Vue from "vue";

const bus = new Vue();
const lastValues = {};
const originalEmit = bus.$emit.bind(bus);
const originalOn = bus.$on.bind(bus);

bus.$emit = function(name, value) {
lastValues[name] = value;
originalEmit(name, value);
};

bus.$on = function(name, callback) {
if (lastValues[name] !== undefined) {
callback(lastValues[name]);
}

originalOn(name, callback);
};

export default bus;

This is, too, sort of hacky, but it most certainly does the trick, remains lighter than Vuex, and is conceptually not too far away from immediately-applied watchers one can define in their components. Let me know your thoughts!