Life-cycle
How to hook into Reflex activity... aka callbacks

Server-Side Reflex Callbacks

StimulusReflex gives you a set of callback events to control how your Reflex actions function. These usual suspects will be familiar to Rails developers:
    before_reflex, around_reflex , after_reflex
    All callbacks can receive multiple symbols representing Reflex actions, an optional block and the following options: only, except, if, unless
    You can abort a Reflex - prevent it from executing - by placing throw :abort in a before_reflex callback. An aborted Reflex will trigger the halted life-cycle stage on the client.
1
class ExampleReflex < StimulusReflex::Reflex
2
# will run only if the element has the step attribute, can use "unless" instead of "if" for opposite condition
3
before_reflex :do_stuff, if: proc { |reflex| reflex.element.dataset[:step] }
4
5
# will run only if the reflex instance has a url attribute, can use "unless" instead of "if" for opposite condition
6
before_reflex :do_stuff, if: :url
7
8
# will run before all reflexes
9
before_reflex :do_stuff
10
11
# will run before increment reflex, can use "except" instead of "only" for opposite condition
12
before_reflex :do_stuff, only: [:increment]
13
14
# will run around all reflexes, must have a yield in the callback
15
around_reflex :do_stuff_around
16
17
# will run after all reflexes
18
after_reflex :do_stuff
19
20
# Example with a block
21
before_reflex do
22
# callback logic
23
end
24
25
# Example with multiple method names
26
before_reflex :do_stuff, :do_stuff2
27
28
# Example with halt
29
before_reflex :run_checks
30
31
def increment
32
# reflex logic
33
end
34
35
def decrement
36
# reflex logic
37
end
38
39
private
40
41
def run_checks
42
throw :abort # this will prevent the Reflex from continuing
43
end
44
45
def do_stuff
46
# callback logic
47
end
48
49
def do_stuff2
50
# callback logic
51
end
52
53
def do_stuff_around
54
# before
55
yield
56
# after
57
end
58
end
Copied!

Client-Side Reflex Callbacks

StimulusReflex gives you the ability to inject custom JavaScript at five distinct moments around sending an event to the server and updating the DOM. These hooks allow you to improve the user experience and handle edge cases.
    1.
    before prior to sending a request over the web socket
    2.
    success after the server side Reflex succeeds and the DOM has been updated
    3.
    error whenever the server side Reflex raises an error
    4.
    halted Reflex canceled by developer with throw :abort in the before_reflex callback
    5.
    after follows either success or error immediately before DOM manipulations
    6.
    finalize occurs immediately after all DOM manipulations are complete
Using life-cycle callback methods is not a requirement.
Think of them as power tools that can help you build more sophisticated results. 👷
If you define a method with a name that matches what the library searches for, it will run at just the right moment. If there's no method defined, nothing happens. StimulusReflex will only look for these methods in Stimulus controllers that extend ApplicationController or have called StimulusReflex.register(this) in their connect() function.
There are two kinds of callback methods: generic and custom. Generic callback methods are invoked for every Reflex action on a controller. Custom callback methods are only invoked for specific Reflex actions.
StimulusReflex also emits life-cycle events which can be captured in other Stimulus controllers, jQuery plugins or even the console.

Understanding Stages

Most of the time, it's reasonable to expect that your Reflexes will follow a predictable cycle: before -> success -> after -> finalize.
There are, however, several important exceptions to the norm.
    1.
    Reflexes that are aborted on the server have a short cycle: before -> halted
    2.
    Reflexes that have errors: before -> error -> after -> [finalize]
    3.
    Nothing Morphs end early: before -> [success] -> after
Nothing Morphs have no CableReady operations to wait for, so there is nothing to finalize. A Nothing Morph with an error will not have a finalize stage.
Nothing Morphs support success methods but do not emit success events.

Generic Life-cycle Methods

StimulusReflex controllers automatically support five generic life-cycle callback methods. These methods fire for every Reflex action handled by the controller.
    1.
    beforeReflex
    2.
    reflexSuccess
    3.
    reflexError
    4.
    reflexHalted
    5.
    afterReflex
    6.
    finalizeReflex
While this is perfect for simpler Reflexes with a small number of actions, most developers quickly switch to using Custom Life-cycle Methods, which allow you to define different callbacks for every action.
In this example, we update each anchor's text before invoking the server side Reflex:
app/views/examples/show.html.erb
1
<div data-controller="example">
2
<a href="#" data-reflex="Example#masticate">Eat</a>
3
<a href="#" data-reflex="Example#defecate">Poop</a>
4
</div>
Copied!
app/javascript/controllers/example_controller.js
1
import ApplicationController from './application_controller.js'
2
3
export default class extends ApplicationController {
4
beforeReflex(anchorElement) {
5
const { reflex } = anchorElement.dataset
6
if (reflex.match(/masticate$/)) anchorElement.innerText = 'Eating...'
7
if (reflex.match(/defecate$/)) anchorElement.innerText = 'Pooping...'
8
}
9
}
Copied!

Custom Life-cycle Methods

StimulusReflex controllers can define up to six custom life-cycle callback methods for each Reflex action. These methods use a naming convention based on the name of the Reflex. The naming follows the pattern <actionName>Success and matches the camelCased name of the action.
The Reflex Example#poke will cause StimulusReflex to check for the existence of the following life-cycle callback methods:
    1.
    beforePoke
    2.
    pokeSuccess
    3.
    pokeError
    4.
    pokeHalted
    5.
    afterPoke
    6.
    finalizePoke
app/views/examples/show.html.erb
1
<div data-controller="example">
2
<a href="#" data-reflex="click->Example#poke">Poke</a>
3
<a href="#" data-reflex="click->Example#purge">Purge</a>
4
</div>
Copied!
app/javascript/controllers/example_controller.js
1
import ApplicationController from './application_controller.js'
2
3
export default class extends ApplicationController {
4
beforePoke(element) {
5
element.innerText = 'Poking...'
6
}
7
8
beforePurge(element) {
9
element.innerText = 'Purging...'
10
}
11
}
Copied!
Adapting the Generic example, we've refactored our controller to capture the before callback events for each anchor individually.
It's not required to implement all life-cycle methods. Pick and choose which life-cycle callback methods make sense for your application. The answer is frequently none.

Conventions

Method Names

Life-cycle callback methods apply a naming convention based on your Reflex actions. For example, the Reflex ExampleReflex#do_stuff will produce the following camel-cased life-cycle callback methods.
    1.
    beforeDoStuff
    2.
    doStuffSuccess
    3.
    doStuffError
    4.
    doStuffHalted
    5.
    afterDoStuff
    6.
    finalizeDoStuff

Method Signatures

Both generic and custom life-cycle callback methods share the same arguments:
    beforeReflex(element, reflex, noop, reflexId)
    reflexSuccess(element, reflex, noop, reflexId)
    reflexError(element, reflex, error, reflexId)
    reflexHalted(element, reflex, noop, reflexId)
    afterReflex(element, reflex, noop, reflexId)
    finalizeReflex(element, reflex, noop, reflexId)
element - the DOM element that triggered the Reflex this may not be the same as the controller's this.element
reflex - the name of the server side Reflex
error/noop - the error message (for reflexError), otherwise null
reflexId - a UUID4 or developer-provided unique identifier for each Reflex

Life-cycle Events

If you need to know when a Reflex method is called, but you're working outside of the Stimulus controller that initiated it, you can subscribe to receive DOM events.
DOM events are limited to the generic life-cycle; developers can obtain information about which Reflex methods were called by inspecting the detail object when the event is captured.
Events are dispatched on the same element that triggered the Reflex. Events bubble but cannot be cancelled.

Event Names

    stimulus-reflex:before
    stimulus-reflex:success
    stimulus-reflex:error
    stimulus-reflex:halted
    stimulus-reflex:after
    stimulus-reflex:finalize
Nothing Morphs do not emit stimulus-reflex:success events.

Event Metadata

When an event is captured, you can obtain all of the data required to respond to a Reflex action:
1
document.addEventListener('stimulus-reflex:before', event => {
2
event.target // the element that triggered the Reflex (may not be the same as controller.element)
3
event.detail.reflex // the name of the invoked Reflex
4
event.detail.reflexId // the UUID4 or developer-provided unique identifier for each Reflex
5
event.detail.controller // the controller that invoked the stimuluate method
6
event.target.reflexData[event.detail.reflexId] // the data payload that will be delivered to the server
7
event.target.reflexData[event.detail.reflexId].params // the serialized form data for this Reflex
8
})
Copied!
event.target is a reference to the element that triggered the Reflex, and event.detail.controller is a reference to the instance of the controller that called the stimulate method. This is especially handy if you have multiple instances of a controller on your page.
Knowing which element dispatched the event might appear daunting, but the key is in knowing how the Reflex was created. If a Reflex is declared using a data-reflex attribute in your HTML, the event will be emitted by the element with the attribute.
You can learn all about Reflex controller elements on the Calling Reflexes page.

jQuery Events

In addition to DOM events, StimulusReflex will also emits duplicate jQuery events which you can capture. This occurs only if the jQuery library is present in the global scope eg. available on window.
These jQuery events have the same name and details accessors as the DOM events.

Promises

Are you a hardcore JavaScript developer? A props power-lifter? Then you'll be pleased to know that in addition to life-cycle methods and events, StimulusReflex allows you to write promise resolver functions:
1
this.stimulate('Comments#create')
2
.then(() => this.doSomething())
3
.catch(() => this.handleError())
Copied!
You can get a sense of the possibilities:
1
this.stimulate('Post#publish')
2
.then(payload => {
3
const { data, element, event } = payload
4
const { attrs, reflexId } = data
5
// * attrs - an object that represents the attributes of the element that triggered the reflex
6
// * data - the data sent from the client to the server over the web socket to invoke the reflex
7
// * element - the element that triggered the reflex
8
// * event - the source event
9
// * reflexId - a unique identifier for this specific reflex invocation
10
})
11
.catch(payload => {
12
const { data, element, event } = payload
13
const { attrs, reflexId } = data
14
const { error } = event.detail.stimulusReflex
15
// * attrs - an object that represents the attributes of the element that triggered the reflex
16
// * data - the data sent from the client to the server over the web socket to invoke the reflex
17
// * element - the element that triggered the reflex
18
// * error - the error message from the server
19
// * event - the source event
20
// * reflexId - a unique identifier for this specific reflex invocation
21
})
Copied!
You can get the reflexId of an unresolved promise:
1
const snail = this.stimulate('Snail#secrete')
2
console.log(snail.reflexId)
3
snail.then(trail => {})
Copied!

Configuring Promise resolution timing

Any Promise can only be resolved once, at which time your callback will run if defined. By default, StimulusReflex will resolve the Promise associated with a Reflex action during the after life-cycle stage. This means your callback will execute after the server has executed the Reflex action but before any DOM modifications are initiated. In some cases, this is too soon to be useful.
You can initiate a Reflex that will resolve its Promise during the finalize life-cycle stage, after all CableReady operations have completed. At this point, all DOM modifications are complete and it is safe to initiate animations or other effects.
To request that a Reflex resolve its Promise during the finalize stage instead of after, pass resolveLate: true as one of the possible optional arguments to the stimulate method.
1
this.stimulate('Example#foo', { resolveLate: true }).then(() => {
2
console.log('The Reflex has been finalized.')
3
}
Copied!

StimulusReflex Library Events

In addition to the Reflex life-cycle mechanisms, the StimulusReflex client library emits its own set of handy DOM events which you can hook into and use in your applications.
    stimulus-reflex:connected
    stimulus-reflex:disconnected
    stimulus-reflex:rejected
    stimulus-reflex:ready
All four events fire on document.
connected fires when the ActionCable connection is established, which is a precondition of a successful call to stimulate - meaning that you can delay calls until the event arrives. It will also fire after a disconnected subscription is reconnected.
disconnected fires if the connection to the server is lost; the detail object of the event has a willAttemptReconnect boolean which should be true in most cases.
rejected is fired if you're doing authentication in your Channel and the subscription request was denied.
ready is slightly different than the first three, in that it has nothing to do with the ActionCable subscription. Instead, it is called after StimulusReflex has scanned your page, looking for declared Reflexes to connect. This event fires every time the document body is modified, and was created primarily to support automated JS test runners like Cypress. Without this event, Cypress tests would have to wait for a few seconds before "clicking" on Reflex-enabled UI elements.

jQuery Events

In addition to DOM events, StimulusReflex will also emits duplicate jQuery events which you can capture. This occurs only if the jQuery library is present in the global scope eg. available on window.
These jQuery events have the same name and details accessors as the DOM events.
Last modified 4mo ago