How to make an effect in Ember?

What’s the equivalent of Solid.js createEffect() (or React useEffect(), Meteor Tracker.autorun(), MobX autorun(), Vue watchEffect(), Svelte $effect(), Angular effect()) in Ember.js?

For example, in Solid.js we can log something any time some signals (similar to tracked variables in Ember) change:

createEffect(() => {
  // This automatically re-runs when a, b, or c change.
  console.log(a(), b(), c())
})

And similar with other mainstream frameworks. It isn’t obvious from the docs how to do this.

Similar topic: Is there a way to a change in a tracked property fire a function - #3 by pabloascarza

1 Like

Here’s an unofficial solution:

https://emberobserver.com/addons/ember-tracked-effects-placeholder

Here’s a StackOverflow question about this, but it doesn’t have the solution for a generic effect:

1 Like

Hello! welcome!! :tada:

This is certainly shocking to folks new to ember, but we deliberately don’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

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

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 sync | reactiveweb 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:

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!

3 Likes

It is a bit hard for me to understand, can u explain in simple terms?