"We ditched React for 80% of our frontend—and productivity skyrocketed."
When Stimulus 3.0 dropped, we were skeptical. Could a 10KB library really replace our React components? Six months later, we’ve deleted 12,000 lines of JavaScript, cut bundle sizes by 60%, and—most surprisingly—our team actually enjoys frontend work again.
Here’s why we’re doubling down, and when you should (and shouldn’t) follow our lead.
1. The Stimulus 3.0 Game Changers
1. Lifecycle Hooks That Finally Make Sense
// No more awkward `initialize` vs `connect` confusion
export default class extends Controller {
initialize() { } // Once per class
connect() { } // When DOM appears
disconnect() { } // Cleanup!
}
Why it matters:
-
Memory leaks solved:
disconnect()
removes event listeners automatically - Turbo compatibility: Seamless handling of cached pages
2. Actions With Superpowers
<!-- Before -->
<div data-action="click->search#submit"></div>
<!-- After: Multiple events, throttling -->
<div data-action="
click->search#submit
input.debounce.300ms->search#autocomplete
"></div>
Killer feature: debounce
and throttle
built-in (no more Lodash dependency).
3. Values That Don’t Suck
// Define types upfront (bye-bye `parseInt` everywhere)
static values = {
timeout: { type: Number, default: 1000 },
enabled: Boolean
}
// Auto-converted!
this.timeoutValue // => 1000 (number)
this.enabledValue // => true/false (boolean)
2. Real-World Wins
Case 1: Replacing a React Auto-Complete
Before (React):
- 45KB component
- Webpack config headaches
- Redux state overkill
After (Stimulus 3.0):
<div data-controller="autocomplete"
data-autocomplete-url-value="/search">
<input data-autocomplete-target="input"
data-action="input.debounce.300ms->autocomplete#fetch">
<ul data-autocomplete-target="results"></ul>
</div>
// 15 lines of code
fetch() {
fetch(`${this.urlValue}?q=${this.inputTarget.value}`)
.then(response => response.json())
.then(json => this.resultsTarget.innerHTML = json.html)
}
Result:
- 90% less code
- Zero bundle dependencies
- Works without JavaScript (progressively enhanced)
Case 2: Turbo Stream Enhancements
// Subscribe to Turbo events
this.application.registerAction(
"turbo:before-stream-render",
(event) => this.highlightChanges(event)
)
Why it rocks:
- Add animations to Turbo Stream updates
- Implement undo toast notifications
- No jQuery.velocity nonsense
3. When Stimulus Isn’t Enough
❌ Complex state management (Use React/Vue)
❌ Pixel-perfect animations (GSAP/WebGL still wins)
❌ Teams married to JSX
Golden Rule:
Use Stimulus for enhancing server-rendered HTML, not building SPAs.
4. Migration Tips
-
Start with these 3.0 features:
- Lifecycle hooks
- Debounced actions
- Typed values
- Use the upgrade tool:
stimulus upgrade ./app/javascript/controllers
- Delete jQuery: Yes, really.
"But Our App Is 100% React!"
Try this:
- Replace one trivial component (e.g., a counter)
- Compare bundle sizes
- Laugh/cry at the difference
Pushed Stimulus to its limits? Share your hacks below!
Top comments (1)
Hi! I read your article — it was amazing, congratulations!
I’m not trying to promote the gem, but just wanted to share something I built for fun over the past few days: github.com/josephschito/opal_stimulus
I’m still not sure if it’s actually a useful idea (I’d love to hear your thoughts on that), but if you’re interested, take a look — I hope you’ll enjoy it! 😉
Again… 👏🏻👏🏻👏🏻