Originally from How to make an effect in Ember?
@trusktr asks:
What’s the equivalent of Solid.js
createEffect()
(or ReactuseEffect()
, MeteorTracker.autorun()
, MobXautorun()
, VuewatchEffect()
, Svelte$effect()
, Angulareffect()
) in Ember.js?
This is certainly shocking to folks new to ember, but ember deliberately doesn't have an any effect by default.
Now, as a framework author, the concept does sort of exist (at a high level) -- but I'll circle back around to this in a smidge.
In your Solid demo, if you want to log function calls, you'd do:
const a = () => 1;
const b = () => 2;
const c = () => 3;
<template>
{{log (a) (b) (c)}}
</template>
Some notes on function invocation syntax, if needed
- Glimmer Tutorial: Transforming Data
- https://cheatsheet.glimmer.nullvoxpopuli.com/docs/templates#template__notation
We use templates as the sole entrypoint to reactivity, whereas solid's reactivity is more general purpose. With templates, and being DOM focused (for now), we can ask ourselves:
"If the user can't see the data rendered, does the data need to exist?"
Now, you're demo (with logging) is definitely effect-y. And if you had no other way (like the situation was somehow impossible to model in a derived data way), you can do this:
function myLog() {
console.log(a(), b(), c());
}
<template>
{{ (myLog) }}
</template>
This would auto-track, so as the consumed tracked data accessed from each of a
, b
, and c
changed, myLog
would get to re-run.
However, this has a caveat: data may not be set within myLog
, else an infinite render loop would occur.
This is covered here
- Glimmer Tutorial: synchronizing state (to the console in this case)
There is a way around the above caveat, not being able to set during render, by making myLog
invoke an async-IIFE, and waiting a tiny bit (i.e.: setting slightly after render):
// now we're passing in the args directly so that they
// are tracked (all args are auto-tracked in all
// function / helper / component / modifier execution
// coming from the template)
function myLog(...args) {
async function run() {
await 0;
// causes a change in a's data
// and because we awaited, we don't infinite loop
setA();
// prints a pre-setA, because a was passed in
console.log(...args);
}
// return nothing, render nothing
// (we have no result to show the user)
}
<template>
{{myLog (a) (b) (c)}}
</template>
This is nuanced, and is why I made this tiny abstraction a whole thing over here https://reactive.nullvoxpopuli.com/functions/sync.sync.html it's 95% documentation, 5% code :sweat_smile:
So coming back to:
"We deliberately don't have effects"
Because of a couple current facts about our ecosystem:
- we want derived data to be preferred, because it is the most efficient way to have your render state settle
- calling a function from a template can only happen after the template is rendered, so doing so causes a second render (I believe this is true in React as well)
- there is a need to synchronize external state, and that has been part of the exploration of Resources, and
Sync
- Starbeam Docs on
Sync
- Starbeam Docs on
Resource
s - Current ember implementation does not have
sync
capabilities: ember-resources (due to limitations of the private APIs implementing reactivity (ember-resources is public-API only)) - Tutorial Chapters on Resources
- Starbeam Docs on
- we think that effects are overused and a huge footgun (for app devs), so by documenting a story more around synchronizing external state, we can continue to guide devs in to a pit of success.
Note: Starbeam is where we're extracting our reactivity primitives, and are planning to swap to Starbeam entirely at some point once we work out some composition ergonomics for serial Resources (the coloring problem).
Hope this helps! If anything is unclear or if you have more questions, let me know!