Feature Idea - Component-API
See original GitHub issueFeature Idea - Stimulus Component API
Executive Summary
Design an API to support mounting of Javascript-Components for very interactive parts of the application WITHOUT compromising the features of Turbo. There’s a demo for vue, react and svelte adapters I built: https://stimulus-component-api.konow.ski/
Introduction
Hotwire allows to implement an SPAish user-experience while only writing server-side rendered HTML, which is cool and efficient. But sometimes I struggle with parts of the UI that are just SUPER-interactive and require way too much custom JS for my taste.
Some examples are:
- maps
- how to structure the code so it doesn’t look like my kitchen when I cook spaghetti?
- multi-step wizards - optionally with alternative routes
- Where to put the state? Put it in session, DOM-attributes, a form or keep it in JS?
- How to handle step-back and -forward? Will it reset or safe the results?
- When, how and where to invalidate the state in case some external parameter is changed?
JS-Frameworks like Vue have very clean ways to structure use-cases like this, for example the vue-leaflet plugin. https://codesandbox.io/s/dd0kit?file=/App.vue:716-1653
But once introducing a JS-Framework other Problems arise:
- Where is my state now? JS or DOM?
- Turbo dynamically injects turbo-frames or -streams. How to know when to initialize and UNinitialize these components?
- Once I’m in Vue- or React-Land, how do I find my way back into the Hotwire ecosystem? (i.e. handing page-flow back to turbo)
I tried solving the before mentioned problems with StimulusJS as it provides lifecycle callbacks, has the Value-API for state-management and integrates nicely with Turbo. I found a few “mounting xxx-JS components with StimulusJS” blog posts: https://www.davedkg.com/stimulusjs-and-mounting-a-react-app or https://gist.github.com/ryenski/c9de022dc94c16131971b64d49c0778d
I basically copy pasted the identical 20 LOC snippets to a hand full of stimulus-controllers the achieve my goal, but I came to the conclusion that “this code is too WET, I need to get it DRY”.
Prototype
To not have to repeat the “mounting”-logic every time I tried to come up with a nice and unified way to mount components to DOM and have their State managed in an “Adapter”-Style. To come up with a more generic approach, I tried to write such an adapter for Vue, React and Svelte - trying to keep the interface consistent.
All adapters implemented have 3 component-lifecycle stages, 3 component functions and 2 framework-references.
Lifecycle Stages:
- create
- mount
- unmount
Functions:
- change:
- the component communicates state-changes bottom-up
- the stimulus-controller communicates state-changes top-down
- action:
- the component can call stimulus-controller actions
- setProperty:
- the stimulus-controller sets some property on the component that’s not managed by the Value-API
Framework references: The adapters should NOT include any React, Vue or Svelte specific code. So the developer has to pass references to integral framework-parts.
- setRenderFunction:
- Vue3 and react need to know how to put HTML in the DOM
- setFactory:
- Vue2 and React need to know how to create a new Component instance
I published a working demo that shows the results. https://stimulus-component-api.konow.ski/ Please also have a look at the git-repo. It needs some more refactoring, but it’s in a presentable state.
Required functionality
StimulusJS would need a new API that allows to
- register component-handlers and
- register framework-components
Example:
app.js
import { ComponentRegistry } from "@hotwired/stimulus"
import { ReactComponent } from "stimulus-react-adapter"
import { createRoot } from 'react-dom/client'
import { createElement } from 'react'
ReactComponent.setFactory(createRoot)
ReactComponent.setRenderFunction(createElement)
ComponentRegistry.register('react', ReactComponent)
my_controller.js
import HelloComponent from "../react/HelloComponent"
export default class extends Controller {
static components = [{
type: 'react', target: 'mountpoint', component: HelloComponent
}]
static values = { text: String, counter: Number }
}
And then stimulus automagically mounts HelloComponent
s to every mountpoint
-target entering the DOM, using the ReactComponent
-Adapter and injects the values as react-props.
The Question
Can you (the maintainers) imagine such a functionality to be part of Hotwire?
I see three ways to implement such a feature:
- entirely outside the hotwire ecosystem: Just like the popular https://stimulus-use.github.io/stimulus-use/#/ extensions.
- Create useReact, useVue and useSvelte and anybody can import it.
- partially inside hotwire ecosystem: component api inside, adapters outside.
- the api (something like I proposed) is implemented in stimulus core and you can then
yarn add stimulus-react-adapter
- All in: Maintaining a component-API and a hand full of popular adapters inside the hotwire ecosystem.
I’d be happy to go for (and contribute to) any approach, but I’d like your Feedback before I produce a (possibly rejected) PR on the stimulus Repository.
Issue Analytics
- State:
- Created a year ago
- Reactions:6
- Comments:9 (1 by maintainers)
Top GitHub Comments
Maybe “island architecture” goes into the right direction, but it feels a bit too heavy right now as an approach - maybe after maturing a bit further I’ll give it a try 😃
Regarding Turbo integration - I’d not drive it that far. When a turbostream yanks in a
<div data-controller="test"><div data-test-target="mountpoint"></div></div>
, I just want it to use the mountpointTarget as a component root. When the target/controller is deleted it should be gracefully unmounted. Nothing fancy there. For the rest we’ve got JSON and HTML-Forms 😃As I got a rails project in maintenance where I mounted a hand full of Vue2 components (just 3 or 4 actually), I finally decided to build the
stimulus-component-api
-package… Now my code looks a bit cleaner and maybe others will enjoy it, too.In case someone wants to fiddle around with it: Check out the NPM-Package or dive into the source
You should just proceed at No.1 now that you see no one has responded. This is a great idea. I think it’s too bad no one responded because allowing for some kind of plugin with Stimulus would just allow us devs to make out house bigger and include more people inside the house which is good for rails et al in general. Nice work!
If you decide to chase down option No. 1 please tag me and I’ll come check it out. Happy to try contribute.