Skip to content

fix: use state instead of source in reactive classes #16239

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

paoloricciuti
Copy link
Member

Closes #16237

So the problem with using source is that:

  1. an $effect/$derived that reads a source created in itself will also depend on it...since normally state created in a reaction doesn't depend on itself this is counter intuitive
  2. if you try to write to a source within the same derived it will throw an error

This is a bit problematic in some cases because we create the sources lazily...in the case of Map for example we don't create a source unless you get or has or set that first. However this means that if you create a new Map in a derived ask if it has something, then you try to write to it in the same derived the newly created source throws the error.

For set it was as simple as changing source to state...however with get and has it's a bit different: the effect needs to have a dependency on the source so state is out of question but then you don't want writes to it to throw. For that my solution was to add the source to the reaction_values after the get (only if the sources was newly created). For #read_all it was a bit more complex and i had to create a WeakSet for the newly created signals (not sure about memory/performance impact of this tho).

I also changed the source's to state's in Tweened and Spring since that also could've been problematic in cases like this

<script>
	import { Spring } from "svelte/motion";
	const map = $derived.by(() => {
			const spring = new Spring(0);
			spring.set(100);
			return spring;
	});
</script>

{map.current}

source is also used inside createSubscriber and I think it could have a problem there too but i need to check to be sure.

It's also used inside await, each and other blocks that "create" a value but i think for that it's probably fine.

As I've said in the issue I'm not satisfied with this solution since it feels a bit hack-ish to add the dependency and still allow the user to write to it.

I also need to add a bunch of tests for this but wanted to give the code to @gyzerok

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Copy link

changeset-bot bot commented Jun 25, 2025

⚠️ No Changeset found

Latest commit: fb627c0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@svelte-docs-bot
Copy link

Copy link
Contributor

Playground

pnpm add https://pkg.pr.new/svelte@16239

@paoloricciuti
Copy link
Member Author

New idea I'll try to implement: store the active reaction in the constructor of the Map/Set and use a state only if it's === to the current active reaction

@paoloricciuti paoloricciuti marked this pull request as ready for review June 25, 2025 21:19
Copy link
Contributor

@gyzerok gyzerok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

@@ -56,13 +56,17 @@ export class SvelteMap extends Map {
#sources = new Map();
#version = state(0);
#size = state(0);
/**@type {Reaction | null} */
#initial_reaction = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if there are cases when map still lives, while the reaction could have been GCed and this would prevent it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was wondering about that too...technically it shouldn't be the case because either there's no active reaction (it's outside of a derived/effect) or it's inside it but this means that when the derived/effect is no longer used the function should be GCd and everything inside it too...but I need to do a better check

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this can become initial_reaction = WeakSet(active_reaction) and we can check with this.initial_reaction.has(active_reaction)?

@gyzerok
Copy link
Contributor

gyzerok commented Jun 26, 2025

Also tried installing this version locally, enabling runes mode forcefully and clicking through the interface. Looks like not hitting unsafe_state_mutation anywhere anymore.

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

Successfully merging this pull request may close these issues.

follow up on state_unsafe_mutation for SvelteMap created inside $derived
2 participants