--- name: better-stimulus description: Apply Better Stimulus best practices for writing maintainable, reusable StimulusJS controllers following SOLID principles --- # Better Stimulus Apply opinionated best practices from [betterstimulus.com](https://betterstimulus.com/) when writing or refactoring Stimulus controllers. These patterns emphasize code reusability, proper separation of concerns, and SOLID design principles. ## When to Use This Skill Invoke this skill when: - Writing new Stimulus controllers - Refactoring existing Stimulus code - Reviewing Stimulus controller architecture - Debugging inter-controller communication - Integrating third-party JavaScript libraries - Implementing form submission logic - Managing controller state - Setting up Turbo integration ## Core Principles ### 1. Make Controllers Configurable **Externalize hardcoded values into data attributes** rather than embedding them in controller logic. Bad: ```javascript toggle() { this.element.classList.toggle("active") } ``` Good: ```javascript static classes = ["active"] toggle() { this.element.classList.toggle(this.activeClass) } ``` ```html
``` ### 2. Use Values API for State **Store controller state in Stimulus values**, not instance properties, to leverage reactivity and DOM persistence. Bad: ```javascript connect() { this.count = 0 } ``` Good: ```javascript static values = { count: Number } countValueChanged(count) { this.updateDisplay() } ``` ### 3. Keep Controllers Focused (Single Responsibility) **Each controller should have one reason to change.** Split controllers that mix concerns. Ask: "What would cause this controller to change?" If multiple unrelated reasons, split it. ### 4. Don't Overuse connect() Use `connect()` for: - Instantiating third-party plugins (Swiper, Chart.js, etc.) - Feature detection/browser capabilities Don't use `connect()` for: - Setting up state (use Values API) - Adding event listeners (use `data-action`) ### 5. Register Events Declaratively **Use `data-action` attributes** instead of `addEventListener()` to let Stimulus manage lifecycle. Bad: ```javascript connect() { document.addEventListener("click", this.handler.bind(this)) } ``` Good: ```html ``` ## Key Patterns ### Architecture - **Configurable Controllers**: Inject dependencies via data attributes - **Application Controller**: Base class for shared functionality - **Mixins**: Share behavior via "acts as" relationships - **Targetless Controllers**: Separate element vs. target manipulation - **Namespaced Attributes**: Handle arbitrary parameter sets See: `references/architecture.md` ### State Management - Use Values API for nearly all state - Leverage change callbacks (`[name]ValueChanged`) - Keep values serializable - Provide sensible defaults See: `references/state-management.md` ### Lifecycle - Use `connect()` for third-party library initialization - Pair `connect()` with `disconnect()` for cleanup - Avoid overloading `connect()` with state setup - Implement `teardown()` for Turbo-specific cleanup See: `references/lifecycle.md` ### Controller Communication Three approaches: 1. **Custom Events**: Loose coupling, broadcast pattern 2. **Outlets**: Direct controller references, structured layouts 3. **Callbacks**: Request state from other controllers Choose based on relationship: - Unknown receivers → Custom events - Known hierarchy → Outlets - Data sharing → Callbacks See: `references/events-and-interaction.md` ### SOLID Principles - **Single Responsibility**: One reason to change - **Open-Closed**: Extend via inheritance, not modification - **Dependency Inversion**: Depend on abstractions, inject via config See: `references/solid-principles.md` ### DOM & Turbo - Use `` to restore DOM state - Use `requestSubmit()` not `submit()` for forms - Implement global teardown for Turbo caching - Handle Turbo events declaratively See: `references/dom-and-turbo.md` ### Error Handling - Create ApplicationController with `handleError()` method - Integrate with error tracking (Sentry, Honeybadger) - Provide user-friendly messages - Use try-catch for async operations See: `references/error-handling.md` ## Quick Reference ### Value Types ```javascript static values = { url: String, count: Number, enabled: Boolean, items: Array, config: Object } ``` ### Event Actions ```html