Skip to content

Commit

Permalink
Mimic React Hook's dependencies comparison (#374)
Browse files Browse the repository at this point in the history
closes #155

This PR changes the comparison behavior to mimic the React Hook dependencies comparison which uses Object.is().

Previously, keys are compared by the == operator. The Dart == operator works differently from the JavaScript Object.is():

Object.is()

Object.is(NaN, NaN) // true
Object.is(0, -0) // false
Dart ==

double.nan == double.nan // false
0 == -0 // true
This PR checks if a list of keys contain a value with the type num and proceeds to handle cases where the value is either NaN or 0.0 and -0.0.
  • Loading branch information
jezsung committed Jul 25, 2023
1 parent 023fefc commit 9bfd33a
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 2 deletions.
5 changes: 4 additions & 1 deletion packages/flutter_hooks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unreleased minor
## Unreleased major

- **Breaking**: `keys` comparison has changed under the following conditions:
- Change from `double.nan` to `double.nan` preserves the state.
- Change from `0.0` to `-0.0` or vice versa does NOT preserve the state.
- Added `useOnStreamChange` (thanks to @jezsung)

## 0.19.0 - 2023-07-10
Expand Down
22 changes: 21 additions & 1 deletion packages/flutter_hooks/lib/src/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ Calling them outside of build method leads to an unstable state and is therefore
///
/// - `hook1.keys == hook2.keys` (typically if the list is immutable)
/// - If there's any difference in the content of [Hook.keys], using `operator==`.
///
/// There are exceptions when comparing [Hook.keys] before using `operator==`:
/// - A state is preserved when one of the [Hook.keys] is [double.nan].
/// - A state is NOT preserved when one of the [Hook.keys] is changed from 0.0 to -0.0.
static bool shouldPreserveState(Hook<Object?> hook1, Hook<Object?> hook2) {
final p1 = hook1.keys;
final p2 = hook2.keys;
Expand All @@ -172,7 +176,23 @@ Calling them outside of build method leads to an unstable state and is therefore
if (!i1.moveNext() || !i2.moveNext()) {
return true;
}
if (i1.current != i2.current) {

final curr1 = i1.current;
final curr2 = i2.current;

if (curr1 is num && curr2 is num) {
// Checks if both are NaN
if (curr1.isNaN && curr2.isNaN) {
return true;
}

// Checks if one is 0.0 and the other is -0.0
if (curr1 == 0 && curr2 == 0) {
return curr1.isNegative == curr2.isNegative;
}
}

if (curr1 != curr2) {
return false;
}
}
Expand Down
49 changes: 49 additions & 0 deletions packages/flutter_hooks/test/use_effect_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,55 @@ void main() {
verifyNoMoreInteractions(disposerA);
verifyNoMoreInteractions(effect);
});

testWidgets(
'does NOT call effect when one of keys is NaN and others are same',
(tester) async {
parameters = [double.nan];
await tester.pumpWidget(builder());

verifyInOrder([
effect(),
unrelated(),
]);
verifyNoMoreInteractions(effect);

parameters = [double.nan];
await tester.pumpWidget(builder());

verifyNoMoreInteractions(effect);
});

testWidgets(
'calls effect when one of keys is changed from 0.0 to -0.0 and vice versa',
(tester) async {
parameters = [0.0];
await tester.pumpWidget(builder());

verifyInOrder([
effect(),
unrelated(),
]);
verifyNoMoreInteractions(effect);

parameters = [-0.0];
await tester.pumpWidget(builder());

verifyInOrder([
effect(),
unrelated(),
]);
verifyNoMoreInteractions(effect);

parameters = [0.0];
await tester.pumpWidget(builder());

verifyInOrder([
effect(),
unrelated(),
]);
verifyNoMoreInteractions(effect);
});
}

class MockEffect extends Mock {
Expand Down

0 comments on commit 9bfd33a

Please sign in to comment.