This post will be a collection of demos I’ve used in response to questions from various community members – for the purpose of finding these again easily, and maybe other folks will f ind them useful as well.
Loading Remote Data
shows how to use loading state
keeps the UI stable while new data is loaded / refresh
Note that effects should be avoided (in any ecosystem, not just ember). They are an easy thing to grab for, but often a symptom of a bigger problem. Most effects I’ve seen (whether implemented as helpers (as in the demos below) or as modifiers (like @ember/render-modifiers) should actually just be a @cached getter. Derived data patterns are much easier to debug and provide much nicer stack traces, whereas effects are “pretty much” observers, which can suffer from “spooky action at a distance” or “weird stacktrace”.
This is really great stuff, thanks for sharing and keeping it updated!
I’m curious on your thoughts re: effects. I’m sure it mostly comes down to semantics (like everything else seems to) but it seems like you’re describing “state effects” or maybe “lifecycle effects” here which are distinct from side effects, no?
E.g. in the React docs there’s an example of changing document title, and then also:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
Seems worth disambiguating unless you’re talking about side-effects too… in which case it may be cool to more explicitly describe/demonstrate patterns for some of those side-effects.
After writing this out, I think I’ve distilled the following:
Effect (the word) is typically a side-effect (tho effect is maybe used a little too loosely)
A side-effect may be defined as:
code that explicitly causes a change to state that is not managed by the code causing the change.
Synchronization is a good use case for “Effects”
I think we may need to provide cookbook-style alternatives for side-effecting code, but these examples are hard to pull out of thin air I may have gotten overwhelmed as I started trying to come up with examples, and just stopped after a couple (I’ve spent enough time on this reply ).
</tldr>
yeah, this is a good point – under the general definition of “effect”, everything kinda gets convoluted / same-y.
Your example you quote about the document.title is a good one.
It’s what ember-page-title does via:
I would categorize this process as “synchronization”, which I believe the React folks are also starting to unify on as the true intent of an “effect” (as of Aug 22, 2022 (via observing on twitter the past few months)).
For how I’d do these in ember, they are explicitly non-effects – but I have often seen them implemented as effecting-code. So, I need to define what I’m considering an “effect to be”, which… I think this works:
code that explicitly causes a change to state that is not managed by the code causing the change.
Examples of things that fit in to this definition (and if you have more examples, we should document them (and alternatives)):
{{did-insert}} / {{did-update}} / {{will-destroy}}
these element modifiers have (nearly) never been used to manage insert/update/destroy behaviors, but instead do other things, like setting @tracked data, creating instances of other objects, managing event listeners, accessing services injected in the component to call other methods – other means of “observer” behavior
alternatives:
custom (maybe local) modifier that semantically combines behaviors
resource (state managed internally)
@cached getter (no custom state at all, purely derived data)
the code within the getter, foo, contains no state and defers all responsibility to some other function to manage cache, error handling, etc – this is more manageable with @cached, as you’d be able to only invoke someAsyncFunction once for all accesses. The side-effect here is that accessingfoo causes repeat creations of state, and conflicting management of tracked propreties (again a little more sane with @cached, but as these behaviors grow in size, they are very dizzying to debug.
alternatives:
resource (state is self-contained)
template-based helper (resources are exactly this, but also usable in JS)
Getters that do this:
class {
get foo() {
if (!this._foo) {
this._foo = new SomeOtherClass();
}
return this._foo;
}
}
This is maybe a bit of a stretch, as _foo is kinda managed by the code doing the side-effect, but is ultimately defined on, and vulnerable to changes from the hosting class – on the surface looks similar to what @cached would do internally, but becomes inanely difficult to manage as you want to add reactivity.
alternatives
resource
@cached getter with no external state.
Should these examples get more specific?
If anyone wants to see alternatives, please post details, and I/we can show non-effecting alternatives.
This feels quite dangerous. It’s totally doable to manage inner DOM in a component, or via modifier (as you would when setting up CodeMirror, Monaco, or any other editor) – but, to me, these are tools where I don’t need to know how they’re implemented, and they themselves could manage DOM via side-effecting, or not. To be clear tho, setting up these tools isn’t itself a side-effect, else all function calls are side-effects. This is direct. Element created → element modifier called → editor installed. And coming back to the definition from above,
code that explicitly causes a change to state that is not managed by the code causing the change.
the state within the editors is totally self-contained.
So, maybe put another way, side-effects are when state leaks out of the “area” where execution began.
Something that I’m realizing, but don’t yet know how to reconcile, as I’m still thinking about this – in none of the examples above did I think a service was the answer. This is because something has to “start” executing code in order for a side-effect to be possible. This is in part how our rendering system, for each {{ ... }}, there is a synchronous evaluation of what represents a value for that curly zone. To invoke a function (async or not) which changes state unrelated to the deriving of the value which will represent this curly zone is a side-effect, and could cause additional rendering as well! Services are good for sharing state across an app, abstracting bigger concepts, etc. But unless state from a service can be derived (async or sync), usage of functions on the service would cause effects (like trying to invoke an ember-concurrency task or ember-data store query or fetch from the template (without some wrapper to manage the invocation and data derivation of the related state to those async behaviors))).
Yeah that all makes sense, thanks for writing up so much in response
I was working from the strictest definition of ‘side effect’ that I’ve heard. For example on wikipedia:
In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, which is to say if it has any observable effect other than its primary effect of returning a value to the invoker of the operation
So triangulating to a javascript app I think that would include everything that isn’t explicitly application state… e.g. browser state, network state (so data fetches would, in that case, be consider side effects), 3rd party library state, etc. That would fit the list from the React docs a little better too I’d think. I guess there’s different state scopes there too… each method/function can have local state, and of course each component has its own scope of state (tracked props, derived data), then there’s the overall application scope of state, then the browser (window, network layer, cache, etc), then the machine the browser is running on (filesystem, memory, etc). So by that stricter definition I’d say a side effect would be code in any of those scopes having an effect in any peer or outer scope. But woof… could go around in circles all day.
ANYWAY I’m not really sure if that’s a meaningful definition for us at the end of the day, that’s just what I was thinking because that’s what I’ve seen before. And so because that was my working definition of effects/side-effects I was curious if you were discouraging modifiers/helpers for those purposes, or just the more narrow category of “local state effects” (if you will). Sounds more like the latter.
FWIW i’m totally onboard with everything you wrote I just wanted to clarify some of the semantics and ideas here. Thank you for engaging. Big fan of your cookbook and demos you have going on here. Let me know if I can help in any way!
A few other references RE: side effect definitions: