MENU
Subscribing to a Backbone Model
A Backbone model announces changes through its own plain event methods, on()/off(), rather than through React state – exactly the kind of external, mutable source useSyncExternalStore was built for.
import React, { useSyncExternalStore } from 'react';
import { createRoot } from 'react-dom/client';
import Backbone from 'backbone';
function useModelField(model, field) {
return useSyncExternalStore(
(callback) => {
model.on('change:' + field, callback);
return () => model.off('change:' + field, callback);
},
() => model.get(field)
);
}
function Item({ model }) {
const text = useModelField(model, 'text');
return <li>{text}</li>;
}
const model = new Backbone.Model({ text: 'A' });
const root = createRoot(document.getElementById('root'));
root.render(<Item model={model} />);Compare this to the useForceUpdate-based Item component in Using Backbone Models in Components. The rewrite above has two concrete advantages: it stays correct under React's concurrent rendering, where a plain forced re-render can't guarantee every consumer of the same model observes it consistently within one render pass; and it takes an optional third getServerSnapshot argument for server rendering, which the manual pattern doesn't address at all. The same swap works for that page's List/collection example too – it just takes a little more care picking a getSnapshot that keeps returning an equal result while nothing has changed and a genuinely different one once membership does, so a simple derived value like collection.length is a safe, simple choice for tracking additions and removals specifically.