Skip to content

Commit

Permalink
Merge pull request #2 from hamsbrar/dev-use-custom-effect
Browse files Browse the repository at this point in the history
Rad: Add missing support for equals(key-comparator) in useEffect
  • Loading branch information
juancastillo0 committed May 31, 2023
2 parents 465db71 + 9f1197f commit 84fcb2a
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 5 deletions.
7 changes: 2 additions & 5 deletions packages/rad_bootstrap/lib/src/rad_bootstrap_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:universal_html/html.dart' as html;
import 'package:rad/rad.dart' as rad;
import 'package:rad/widgets_html.dart' as rad_html;
import 'package:rad_hooks/rad_hooks.dart' as rad_hooks;
import 'package:rad_bootstrap/src/rad_custom_hooks.dart' as rad_custom_hooks;
import 'package:bootstrap_dart/bootstrap/bootstrap_renderer.dart';

class RadBootstrapRenderer implements BootstrapRenderer<rad.Widget> {
Expand Down Expand Up @@ -89,11 +90,7 @@ class RadBootstrapContext extends BootstrapBuildContext {
List<Object?>? keys,
bool Function(Object? p1, Object? p2)? equals,
]) {
rad_hooks.useEffect(
effect,
keys,
// TODO: equals ?? rad_hooks.defaultKeysEquals,
);
rad_custom_hooks.useCustomEffect(effect, keys, equals);
}

@override
Expand Down
73 changes: 73 additions & 0 deletions packages/rad_bootstrap/lib/src/rad_custom_hooks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:rad/rad.dart';
import 'package:rad_hooks/rad_hooks.dart' as rad_hooks;

typedef NullableVoidCallback = VoidCallback? Function();

/// [rad_hooks.useEffect] with custom dependency diffing.
///
void useCustomEffect<T>(
NullableVoidCallback effectCallback, [
List<T>? dependencies,
bool Function(Object? p1, Object? p2)? equals,
]) {
var isInitialRenderRef = rad_hooks.useRef(true);
var dependenciesRef = rad_hooks.useRef(dependencies);
var cleanupCallbackRef = rad_hooks.useRef<VoidCallback?>(null);

rad_hooks.useEffect(() {
// if initial render, run callback
if (isInitialRenderRef.value) {
isInitialRenderRef.value = false;
cleanupCallbackRef.value = effectCallback();

return null;
}

// for subsequent renders, run callback only if dependencies have changed.
if (_isAnyDependencyChanged(
equals: equals,
dependenciesSetOne: dependenciesRef.value,
dependenciesSetTwo: dependencies,
)) {
dependenciesRef.value = dependencies;

cleanupCallbackRef.value?.call();
cleanupCallbackRef.value = effectCallback();
}

return null;
}, null); // null ensures that this useEffect runs on every render.

// this useEffect will ensure that cleanup callback(if any) gets dispatched when scope unmounts
rad_hooks.useEffect(() => () => cleanupCallbackRef.value?.call(), const []);
}

/// We consider dependency sets to be different:
///
/// - if one/both sets are null.
/// - if sets are of different lengths.
/// - if [equals] return false for an item(during pair-wise comparison).
///
bool _isAnyDependencyChanged<T>({
bool Function(Object? p1, Object? p2)? equals,
required List<T>? dependenciesSetOne,
required List<T>? dependenciesSetTwo,
}) {
if (null == dependenciesSetOne || null == dependenciesSetTwo) {
return true;
}

if (dependenciesSetOne == dependenciesSetTwo) {
return false;
}

if (dependenciesSetOne.length != dependenciesSetTwo.length) {
return true;
}

var index = 0;
var keyEquals = equals ?? (Object? p1, Object? p2) => p1 == p2;
return dependenciesSetOne.any((element) {
return !keyEquals(element, dependenciesSetTwo.elementAt(index++));
});
}

0 comments on commit 84fcb2a

Please sign in to comment.