DEV Community

Alex Aslam
Alex Aslam

Posted on

Stimulus 3.0: Why We’re All-In

"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!
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// 15 lines of code
fetch() {
  fetch(`${this.urlValue}?q=${this.inputTarget.value}`)
    .then(response => response.json())
    .then(json => this.resultsTarget.innerHTML = json.html)
}
Enter fullscreen mode Exit fullscreen mode

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)
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • Delete jQuery: Yes, really.

"But Our App Is 100% React!"
Try this:

  1. Replace one trivial component (e.g., a counter)
  2. Compare bundle sizes
  3. Laugh/cry at the difference

Pushed Stimulus to its limits? Share your hacks below!

Top comments (1)

Collapse
 
josephschito profile image
Joseph Schito

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… 👏🏻👏🏻👏🏻