dai-shi/proxy-memoize

Can shallowEqual be avoided?

Noitidart opened this issue · 5 comments

In this case here, I'm not able to avoid shallowEqual.

Here are my states:

const state0 = { winners: [] };
const state1 = { winners: [{ name: 'Bar' }] };
const state2 = { winners: [{ name: 'Bar' }, { name: 'Foo' }] };
const state3 = { winners: [{ name: 'Bar' }, { name: 'Foo' }, { name: 'Foo' }] };

We see in state0 we have empty array.
In state1 we add Bar.
In state2 we add Foo.
In state3 we add Foo again.

Here are my selectors:

const selectWinnerValues = state => {
  console.log('selecting winner values');
  return state.winners;
};
const selectUniqueNames = memoize(winnerValues => {
  console.log('selecting unique names');
  // this here de-dupes the names
  return [...new Set(winnerValues.map(v => v.name))];
});

Now running the selectors on the states:

const return0 = calcWinnerNames(selectWinnerValues(state0));
const return1 = calcWinnerNames(selectWinnerValues(state1));
const return2 = calcWinnerNames(selectWinnerValues(state2));
const return3 = calcWinnerNames(selectWinnerValues(state3));

I will now log out values:

// { return0: [], return1: ['Bar'], isEqual: false }
console.log(JSON.stringify({ return0, return1, isEqual: return0 === return1 }));

// { return1: ['Bar'], return2: ['Bar', 'Foo'], isEqual: false }
console.log(JSON.stringify({ return1, return2, isEqual: return1 === return2 }));

// this last one is unexpected, it should be the same value
// { return2: ['Bar', 'Foo'], return3: ['Bar', 'Foo'], isEqual: false }
console.log(JSON.stringify({ return2, return3, isEqual: return2 === return3 }));

We see return2 and return3 are both ['Bar', 'Foo']. However they are referentially different.

Is there a way in this case, to get referentially identical values when both string values are same?

Is there a way in this case, to get referentially identical values when both string values are same?

I don't think so. So, what this lib does is to track the property access and if any of the accessed property value is changed, it will re-compute.

We can create a much simpler example.

const fn = memoize(x => ({ sum: x.a + x.b }));

console.log(fn({ a: 1, b: 2 }) === fn({ a: 1, b: 2 })); // true
console.log(fn({ a: 1, b: 2 }) === fn({ a: 2, b: 1 })); // false

So, again the mental model can be different. In this case, we'd need custom equality function to compare the results. Not sure if we call it a limitation or a nature.

Oh no problem. So I think I'll just have to always to useSelector with the 2nd argument always being shallowEqual.

Thanks!

Oh should I update the readme to say that we should always do shallowEqual with useSelector to avoid the above problem?

image

we should always do shallowEqual with useSelector

I think not. It should require shallowEqual only if it's the problem.

We have various options to resolve it like returning a primitive.
Furthermore, if it's two-level deep, shallowEqual doesn't work anyway.
So, it really depends. I would guess it's a rare case to require equalityFn, so if I were to add a note, it would be described as a exceptional note (after other better options maybe).

Thanks for your deep consideration.