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

$state() of Array : incorrect behavior of indexOf()/findIndex() with object #11556

Open
adiguba opened this issue May 11, 2024 · 11 comments
Open
Assignees
Milestone

Comments

@adiguba
Copy link
Contributor

adiguba commented May 11, 2024

Describe the bug

When we used an $state([]), some basic function of Array have an incorrect behavior with object.
It seems that all equality operations fail, as the object in the array is a proxy.

	let array = $state([]);
	
	const obj = { data: "any value" };
	array.push(obj);
	
	console.log( array[0] === obj );                 // false !!!
	console.log( array.indexOf(obj) );               // -1 !!!
	console.log( array.findIndex(n => n === obj) );  // -1 !!!

It’s really disturbing and not easy to understand...

Reproduction

Exemple in REPL : the buttons addRandomNumber, addRandomString and addRandomObject add a number/string/object in an array, and logs the index.

This works as espected for number and string, but fails for objects.

REPL

Logs

No response

System Info

REPL

Severity

annoyance

@longnguyen2004
Copy link

longnguyen2004 commented May 11, 2024

Maybe use $state.frozen instead? REPL

As I understand it, there's no way to both make an object/array deeply reactive and maintain equality with the original object/array at the same time, since you have to add extra stuff to make it reactive, and you wouldn't want those extra stuff to propagate back to the original object.

@trueadm
Copy link
Contributor

trueadm commented May 11, 2024

I think this is more a documentation problem. Svelte uses proxies to make deep reactivity work, and thus equality of things you put in state will never match the same thing as what you put in, because they're all wrapped with proxies.

@trueadm trueadm added this to the 5.0 milestone May 11, 2024
@7nik
Copy link

7nik commented May 11, 2024

The workaround seems to reactify the searched value: the $state never creates wrappers (not sure how reliable this behaviour is).
Basically:

const value = {};
$state(value) === $state(value); // true
$state(value) === $state($state(value)); // true

const arr = $state([ value ]);
arr.includes($state(value)); // true

REPL

The proxy could alter the behaviour of search methods, but it won't resolve cases like arrState[0] == rawObject.

@trueadm
Copy link
Contributor

trueadm commented May 14, 2024

@dummdidumm I feel like the new warnings largely address this issue now.

@dummdidumm
Copy link
Member

dummdidumm commented May 14, 2024

There needs to be more docs on this, the warning itself is somewhat vague without any additional explanation IMO. It could come as part of the general "more details on a warning" system we can put in place with how warnings are setup now.

@trueadm
Copy link
Contributor

trueadm commented May 14, 2024

There needs to be more docs on this, the warning itself is somewhat vague without any additional explanation IMO. It could come as part of the general "more details on a warning" system we can put in place with how warnings are setup now.

It might be worth documenting now whilst it’s fresh in your head. I don’t personally think it’s all that needed so maybe your documentation can help me understand your thought process.

@longnguyen2004
Copy link

This should be addressed by #11613

@dm-de
Copy link

dm-de commented May 15, 2024

This should be addressed by #11613

	console.log( array[0] === obj );                 // false !!!
	console.log( array.indexOf(obj) );               // -1 !!!
	console.log( array.findIndex(n => n === obj) );  // -1 !!!

Here is only for 1st line a solition in Svelte 5.
For other two lines - here is nothing, but we can use a for loop.

I'm unhappy about so many extra runes...
It looks like React now - solution for self created problems.

@trueadm
Copy link
Contributor

trueadm commented May 15, 2024

@dm-de It's not a problem if you reference things in state from the beginning. If you don't then you'll need to use $state.is. This is just how JavaScript proxies work, if you'd rather not use them, and you'd rather use immutable objects instead then you have $state.frozen where you don't need to worry about proxies.

@adiguba
Copy link
Contributor Author

adiguba commented May 27, 2024

#11613 add a partial fix for this, and now these lines will produce a warning state_proxy_equality_mismatch :

items.findIndex(n => n === item);  // WARN state_proxy_equality_mismatch

This is fine, and the warning message is explicit enough :

Reactive $state(...) proxies and the values they proxy have different identities.
Because of this, comparisons with === will produce unexpected results.
Consider using $state.is(a, b) instead

=> So a === b should be replaced by $state.is(a, b) :

- items.findIndex(n => n === item);
+ items.findIndex(n => $state.is(n, item));

👍

But for indexOf the warning is ambiguous :

items.indexOf(item);  // WARN state_proxy_equality_mismatch

Reactive $state(...) proxies and the values they proxy have different identities.
Because of this, comparisons with array.indexOf(...) will produce unexpected results.
Consider using $state.is(a, b) instead

=> How do we replace array.indexOf(...) with $state.is(a, b) ???

In fact here we should replace the indexOf() by a findIndex() :

- items.indexOf(item);
+ items.findIndex(n => $state.is(n, item));

So :

  • The message for indexOf() should be more explicit.
  • I still think Svelte should specifically handle calls to indexOf()/lastIndexOf().

In "pseudo-code", the indexOf() of an reactive array should be equivalent to :

indexOf(item) {
   if (item!=null && typeof item === 'object) {
      // item is an object, use findIndex()
      return this.findIndex(n => $state.is(n, item));
   } else {
      return /* original indexOf() */
   }
}

@longnguyen2004
Copy link

I still think Svelte should specifically handle calls to indexOf()/lastIndexOf()

We're getting into monkey-patching territory with this, and I'm pretty sure everyone agrees that we shouldn't do that.

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

No branches or pull requests

6 participants