PoC: Loading components in an included Turbo Frame
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 20/100
- Issue type
- Feature
- Clarity
- Needs clarification
- Activity status
- Stale
- Tech stack
- rails, react, typescript
- Domain
- frontend
Research direction
Review the MutationObserver PoC in the application.js pack and the existing ReactRailsUJS mounting and unmounting behavior alongside Turbo Frame DOM changes. The issue does not name target files or tests, define acceptance criteria, or specify what “done” means.
Written by the indexing model from the issue text.
Description
Hey all,
Like many others, I've been experimenting with Turbo and Turbo Frames. We have a large set of one-off React components we like to sprinkle into our templates using react-rails and that pattern works great for enhancing interactivity.
But, like many others, we quickly realized that Turbo doesn't emit any events when a frame loads. This is by design and their logic makes sense.
To fix this we've put together a quick proof of concept MutationObserver that watches the document tree for changes. These changes could come from any source, but in our case it's always a Turbo Frame load. I don't think there any need to differentiate based on the source of the change.
import React from 'react'
import ReactDOM from 'react-dom'
declare const ReactRailsUJS
document.addEventListener("DOMContentLoaded", () => {
const findComponents = (childNodes: NodeList, testFn: (n: Node) => Boolean, nodes: Node[] = []): Node[] => {
for (let child of childNodes) {
if (child.childNodes.length > 0) {
nodes = findComponents(child.childNodes, testFn, nodes)
} else if (testFn(child)) {
nodes = nodes.concat([child])
}
}
return nodes
}
const mountComponents = (nodes: Node[]) => {
for (let child of nodes) {
const className = (child as Element).getAttribute(ReactRailsUJS.CLASS_NAME_ATTR)
if (className) {
// Taken from ReastRailsUJS as is.
const constructor = ReactRailsUJS.getConstructor(className)
const propsJson = (child as Element).getAttribute(ReactRailsUJS.PROPS_ATTR)
const props = propsJson && JSON.parse(propsJson)
// Improvement:
// Was this component already rendered? Just hydrate it with the props coming in.
// This is currently acceptable since all our components are expected to be reset
// on page navigation.
const component = React.createElement(constructor, props) as any
ReactDOM.render(component, child as Element)
}
}
}
const callback = function (mutationsList: MutationRecord[], observer: MutationObserver) {
const start = performance.now()
console.log("ReactRails: Mutation callback started...", mutationsList)
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length > 0) {
const mountableNodes = findComponents(mutation.addedNodes, (child) => {
return !!(child as HTMLElement).dataset?.reactClass
})
mountComponents(mountableNodes)
}
}
}
console.log("ReactRails: Mutation callback complete.", performance.now() - start)
};
const observer = new MutationObserver(callback)
console.log("ReactRails: Start mutation observer...")
observer.observe(document, { childList: true, subtree: true })
})
We've simply added this to our application.js pack file and we've found that this works quite well.
Hopefully this helps someone else out and/or starts a discussion about moving react-rails to this model for mounting/unmounting components. It's a lot more robust than watching for Turbo events, I think, but I'm sure there's a ton of edge cases covered by the existing code.
Cheers!
- Dominant language
- JavaScript
- Stars
- 6.8k
- Forks
- 739
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from reactjs/react-rails
-
Documentation
Difficulty 4/5 3-5 days Newbie friendliness 45/100
reactjs/react-rails#1380 · 1 comment ·
-
Difficulty 4/5 3-5 days Newbie friendliness 38/100
reactjs/react-rails#1355 · 1 comment · 1 reaction ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
reactjs/react-rails#1351 · 4 comments · 10 reactions ·
-
discussion
Difficulty 4/5 3-5 days Newbie friendliness 35/100
reactjs/react-rails#1341 · 2 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 25/100
reactjs/react-rails#1338 · 3 comments ·
All issues in reactjs/react-rails
Similar issues
-
code-quality refactoring
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
github/gh-aw-firewall#8816 ·
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/deepagents#6450 ·
-
optimization optimization:agents-md-curator
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
githubnext/gh-aw-cao#13143 ·
-
status: needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100