Skip to content

Allow $derived to optionally receive callbacks #9984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Thiagolino8 opened this issue Dec 22, 2023 · 6 comments
Closed

Allow $derived to optionally receive callbacks #9984

Thiagolino8 opened this issue Dec 22, 2023 · 6 comments
Labels

Comments

@Thiagolino8
Copy link

Describe the problem

Currently $derived only receives values, which maintains a cleaner syntax when using expressions
But when it is necessary to use any minimally more complex logic, it is necessary to get out of your mental model and write much less natural code, extracting the logic into a separate named function (naming is difficult), or creating an IIFE

// This is nice
const double = $derived(count * 2)

// This is abominable
function calcTotal() {
  const discountAmount = cartTotal * promoDiscount;
  const taxedAmount = (cartTotal - discountAmount) * taxRate;

  return cartTotal - discountAmount + taxedAmount;
}

const total = $derived(calcTotal())

// I wouldn't pass this on a PR
const total = $derived((() => {
  const discountAmount = cartTotal * promoDiscount;
  const taxedAmount = (cartTotal - discountAmount) * taxRate;

  return cartTotal - discountAmount + taxedAmount;
})());

Describe the proposed solution

Unlike #9250 and #9968 that suggest changes to the api or a new method, I suggest an addition, supporting callbacks using function overloading
This way it would be possible to keep the simplest syntax for expressions and use callbacks only when necessary
Something like:

function<T>$derived(value: T)
function<T>$derived(value: () => T)
function<T>$derived(value: T | () => T) {
  if (value instanceOf Function) return value();
  return value
}

Not only would this facilitate the use of more complex logic, it would also make it possible to receive reactive values from functions that return callbacks

// utils.svelte.js
export const previousState = (value) => {
  let cur
  let prev = $state()

  $effect.pre(() => {
    prev = cur
    cur = value()
  });

  return () => prev
}

// App.svelte
<script>
  import {previousState} from "./utils.svelte.js"
	
  let value = $state('');

  const prev = $derived(previousState(() => value))
</script>

<input bind:value />
<p>Previous State: {prev}</p>

Alternatives considered

Continue writing code unnaturally by extracting into named functions or IIFEs

Importance

nice to have

@Conduitry
Copy link
Member

Checking at compile time (or, as would be required in your last example, at runtime) whether an expression is a function and treating that differently seems like a bad idea to me.

What happens when you want your derived value to itself be a function? Wrap it in another function layer?

@brunnerh
Copy link
Member

Overloads are generally not a good idea, so I am much more in favor of the separation approach (#9968).

This would cause unnecessary confusion, especially when people try to use $derived with snippets or components.

const component = $derived(condition ? ComponentA : ComponentB);
const snippet = $derived(condition ? snippetA : snippetB);

They are functions and the incorrect call will throw an exception.

@SomaticIT
Copy link
Contributor

SomaticIT commented Dec 22, 2023

Overloads are generally not a good idea, so I am much more in favor of the separation approach (#9968).

This would cause unnecessary confusion, especially when people try to use $derived with snippets or components.

const component = $derived(condition ? ComponentA : ComponentB);
const snippet = $derived(condition ? snippetA : snippetB);

They are functions and the incorrect call will throw an exception.

You're right, I think the solution described in #9968 is more appropriate.

@Thiagolino8
Copy link
Author

Overloads are generally not a good idea, so I am much more in favor of the separation approach (#9968).

This would cause unnecessary confusion, especially when people try to use $derived with snippets or components.

const component = $derived(condition ? ComponentA : ComponentB);
const snippet = $derived(condition ? snippetA : snippetB);

They are functions and the incorrect call will throw an exception.

I don't mind about solution #9968, but I don't really believe that overloading would make things confusing
If you want the value of a derived state to be a function, you simply return it from a callback

const component = $derived(() => condition ? ComponentA : ComponentB);
const snippet = $derived(() => condition ? snippetA : snippetB);

@7nik
Copy link
Contributor

7nik commented Dec 23, 2023

It's a bad proposal because it adds a mental load on the developer - you need to think about the type you pass to $derived, plus it's a potential source of nasty bugs. And some people/projects don't use TS to prevent these problems while writing/refactoring the code.

@gtm-nayan
Copy link
Contributor

Closing in favor of #9968

@gtm-nayan gtm-nayan closed this as not planned Won't fix, can't repro, duplicate, stale Jan 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants