Skip to content
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

Function changing state in class does not trigger reactivity #15098

Closed
ecstrema opened this issue Jan 23, 2025 · 8 comments
Closed

Function changing state in class does not trigger reactivity #15098

ecstrema opened this issue Jan 23, 2025 · 8 comments

Comments

@ecstrema
Copy link
Contributor

Describe the bug

I can't seem to wrap my head around svelte's new reactivity system.

Below is a simple example, and I don't understand why it is not working. You just create a state class:

class Test {
  ready = $state(false);

	init() {
		this.ready = true
	}
}

export const test = new Test();

and import it in the other component.

<script>
	import {test} from "./test.svelte"
</script>

<h1>{test.ready}</h1>

<button onclick={test.init}>Click this to set the above to true</button>

But the UI doesn't update.

Reproduction

https://svelte.dev/playground/0d52f8525fbf4e4382eda285c426aec7?version=5.19.2

Logs

System Info

REPL

Severity

annoyance

@Conduitry
Copy link
Member

You need to use onclick={() => test.init()}, otherwise this isn't bound correctly when calling the method.

@Conduitry Conduitry closed this as not planned Won't fix, can't repro, duplicate, stale Jan 23, 2025
@ecstrema
Copy link
Contributor Author

Oh thanks.

So if this function comes from a library and I have no way to know if it is reactive, I need to do this too...

It seems hard, but wouldn't there be a way for the compiler to change all event={fx} to event={() => fx()} during development, and then issue a warning when it detects that it should be reactive?

@Conduitry
Copy link
Member

You should be doing () => foo.bar() generally instead of foo.bar, regardless of whether reactivity is involved. foo.bar won't actually call the bar() method specifically on foo. If you look at something like this, where there's a setInterval() to output the value of test.ready every second, you'll see that it actually doesn't change when you click the button. Svelte's reactivity is not involved. This is just how calling methods in JS works. You can't do const method = object.method; console.log(method()); have expect it to be the same as console.log(object.method());.

@ecstrema
Copy link
Contributor Author

ecstrema commented Jan 23, 2025

Well even better then!

That means you could always issue a warning when someone puts just foo.bar instead of () => foo.bar()

@Prinzhorn
Copy link
Contributor

No, because if you don't rely on this it doesn't matter and you can use foo.bar just fine.

However, I do agree that in dev Svelte could inject code into all class methods that warn you that a method was called with the wrong context. I know that this is a JavaScript "feature" and people "should know", but Svelte could also make the dx here better. @Conduitry thoughts? Are there legitimate reasons that you'd want to call a class method with a different this? I doubt it, as this also breaks TypeScript assumptions.

@ecstrema
Copy link
Contributor Author

No, because if you don't rely on this it doesn't matter and you can use foo.bar just fine.

A warning is not an error though. It's meant for best practices. I think if we can have strong a11y warnings, we can have strong warnings for dx too.

@ecstrema
Copy link
Contributor Author

However, I do agree that in dev Svelte could inject code into all class methods that warn you that a method was called with the wrong context.

I don't think this should be in class methods. I think it should be at binding sites: replace

<button onclick={test.init}>Click this to set the above to true</button>

with

<button onclick={checkUnboundSubscriptions(test.init, loc)}>Click this to set the above to true</button>

and

function checkUnboundSubscriptions(fx, loc) {
	const prev = getSubscriptions()
	fx()
	if (!getSubsriptions() = prev) {
		issueWarning(`${loc} - a state susbbscription happened into an unbound class function. use event={() => fx()} instead of event={fx} to track reactive state. See also https://github.com/sveltejs/svelte/issues/15098#issuecomment-2610943014`)
	}
}

This is of course an overly simplified view, but I think this would save countless hours of developer time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants