Forcing an iframe to rerender with React

You may one day find yourself unlucky enough to work on a React application embedding an iframe. If that happens, chances are you might want to force it to refresh based on some change in the overlying component, that is to say have it rerender. But how would you do it?

An easy and not too shabby way is to abuse the key attribute, which serves as an identifier to tell a React component whether a given child element needs to be rerendered, and is typically used when building a list within a loop. How convenient! And even more so if you hide away all those shenanigans in a custom hook:

import { useState, useEffect } from "react";

export default function useChecksum(value) {
const [checksum, setChecksum] = useState(0);

useEffect(() => {
setChecksum(checksum => checksum + 1);
}, [value]);

return checksum;
}

The "checksum" here is but a dumb counter, which does a perfect job at yielding unique values over time - you can go with something more convoluted if you so desire. Anyhow, here is our hook in action:

import React from "react";
import useChecksum from "../hooks/useChecksum";

export default function FrameContainer({ someProp }) {
const checksum = useChecksum(someProp);

return (
<>
<iframe key={checksum} src="..." title="I'm an outdated piece of technology!" />
</>
);
}

The iframe will thus be refreshed when someProp changes. If you have any better way to deal with this, please let me know!

Bundling a Phaser project with Parcel

As opposed to most contemporary web-oriented JavaScript ecosystems, the game development framework Phaser does not come with a bundling solution out of the box, which would allow the use of cutting-edge syntax, or writing code across multiple source files but still referencing only one in the HTML page hosting the project. Fortunately, adding one in the mix is fairly easy and works just as well as one would expect.

There is, however, one tiny caveat; loading an asset (e.g. an image) implies providing its relative path as a string, delegating the underlying logic to Phaser itself:

import Phaser from "phaser";

export default new Phaser.Class({
// ...

preload() {
this.load.image("tiles", "../../assets/map/spritesheet.png");
}
});

In the context of a built project, the above just might fail, since our PNG file will be out of reach from its scope. In the case of Parcel, the way to go is pretty straightforward, and consists of bundling these assets with the code and using its internal mechanism to get a proper reference to their actual location:

import Phaser from "phaser";
import tiles from "../../assets/map/spritesheet.png";

export default new Phaser.Class({
// ...

preload() {
this.load.image("tiles", tiles);
}
});

A similar solution is probably available with other bundlers. Finally, one might argue they would probably be better off loading those assets statically, but that seems to be a somewhat controversial topic with Parcel.

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!