DEV Community

NullVoxPopuli
NullVoxPopuli

Posted on

Effects in Ember

Originally from How to make an effect in Ember?

@trusktr asks:

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?

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 😅


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
  • 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!

Top comments (0)