diff --git a/CHANGELOG.md b/CHANGELOG.md index adf7a5114..e7af98544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # OverReact Changelog +## [3.12.1](https://github.com/Workiva/over_react/compare/3.12.0...3.12.1) +- [#643] Use `propsOrStateMapsEqual` in `memo` so that function tearoffs don't cause unnecessary rerenders. + ## [3.12.0](https://github.com/Workiva/over_react/compare/3.11.0...3.12.0) - [#641] Expose new event helper APIs. In react-dart, using the `SyntheticEvent` class constructors were deprecated. New event helpers were added as a replacement, and to make their usage convenient, these helpers have been exposed directly via OverReact. diff --git a/lib/src/util/memo.dart b/lib/src/util/memo.dart index 449e588ef..c0051a2a5 100644 --- a/lib/src/util/memo.dart +++ b/lib/src/util/memo.dart @@ -15,6 +15,7 @@ library over_react.memo; import 'package:over_react/src/component_declaration/component_type_checking.dart'; +import 'package:over_react/src/util/equality.dart'; import 'package:react/react_client/react_interop.dart' as react_interop; import 'package:react/react_client.dart'; import 'package:over_react/component_base.dart'; @@ -32,25 +33,25 @@ import 'package:over_react/component_base.dart'; /// ```dart /// import 'package:over_react/over_react.dart'; /// -/// UiFactory MemoExample = memo(uiFunction( +/// UiFactory MemoExample = memo(uiFunction( /// (props) { /// // render using props /// }, -/// UiFactoryConfig(displayName: 'MemoExample'), +/// $MemoExampleConfig, // ignore: undefined_identifier /// )); /// ``` /// /// `memo` only affects props changes. If your function component wrapped in `memo` has a /// `useState` or `useContext` Hook in its implementation, it will still rerender when `state` or `context` change. /// -/// By default it will only shallowly compare complex objects in the props map. +/// By default it will only shallowly compare complex objects in the props map using [propsOrStateMapsEqual]. /// If you want control over the comparison, you can also provide a custom comparison /// function to the [areEqual] argument as shown in the example below. /// /// ```dart /// import 'package:over_react/over_react.dart'; /// -/// UiFactory MemoWithComparison = memo(uiFunction( +/// UiFactory MemoWithComparison = memo(uiFunction( /// (props) { /// // render using props /// }, @@ -80,7 +81,7 @@ UiFactory memo(UiFactory factory, hoc = react_interop.memo2(factory().componentFactory, areEqual: wrapProps); } else { - hoc = react_interop.memo2(factory().componentFactory); + hoc = react_interop.memo2(factory().componentFactory, areEqual: propsOrStateMapsEqual); } setComponentTypeMeta(hoc, diff --git a/pubspec.yaml b/pubspec.yaml index 1de359df4..309e53c0d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: over_react -version: 3.12.0 +version: 3.12.1 description: A library for building statically-typed React UI components using Dart. homepage: https://github.com/Workiva/over_react/ authors: diff --git a/test/over_react/component/memo_test.dart b/test/over_react/component/memo_test.dart index dc6886104..4d9fbc2e7 100644 --- a/test/over_react/component/memo_test.dart +++ b/test/over_react/component/memo_test.dart @@ -25,6 +25,7 @@ part 'memo_test.over_react.g.dart'; int renderCount = 0; mixin FunctionCustomPropsProps on UiProps { int testProp; + Function() testFuncProp; } UiFactory FunctionCustomProps = uiFunction( @@ -84,6 +85,24 @@ main() { expect(renderCount, equals(1)); }); + // Asserts that propsOrStateMapsEqual is being used for the equality check + // when the consumer does not provide a custom `areEqual` parameter. + test('memoizes properly when a tear-off is passed as a function prop value', () { + renderCount = 0; + UiFactory FunctionCustomPropsMemo = memo(FunctionCustomProps); + void handleTestFunc() {} + void handleTestFunc2() {} + final testJacket = mount((FunctionCustomPropsMemo()..testFuncProp = handleTestFunc)()); + testJacket.rerender((FunctionCustomPropsMemo()..testFuncProp = handleTestFunc)()); + testJacket.rerender((FunctionCustomPropsMemo()..testFuncProp = handleTestFunc)()); + + expect(renderCount, equals(1), reason: 'An identical tear-off value should not result in a re-render'); + + testJacket.rerender((FunctionCustomPropsMemo()..testFuncProp = handleTestFunc2)()); + + expect(renderCount, equals(2), reason: 'A different tear-off value should result in a re-render'); + }); + test('memoizes based on areEqual parameter', () { renderCount = 0; UiFactory FunctionCustomPropsMemo = diff --git a/test/over_react/component/memo_test.over_react.g.dart b/test/over_react/component/memo_test.over_react.g.dart index a6cd017df..69b317a94 100644 --- a/test/over_react/component/memo_test.over_react.g.dart +++ b/test/over_react/component/memo_test.over_react.g.dart @@ -181,17 +181,30 @@ mixin $FunctionCustomPropsProps on FunctionCustomPropsProps { @override set testProp(int value) => props[_$key__testProp__FunctionCustomPropsProps] = value; + @override + Function() get testFuncProp => + props[_$key__testFuncProp__FunctionCustomPropsProps] ?? + null; // Add ` ?? null` to workaround DDC bug: ; + @override + set testFuncProp(Function() value) => + props[_$key__testFuncProp__FunctionCustomPropsProps] = value; /* GENERATED CONSTANTS */ static const PropDescriptor _$prop__testProp__FunctionCustomPropsProps = PropDescriptor(_$key__testProp__FunctionCustomPropsProps); + static const PropDescriptor _$prop__testFuncProp__FunctionCustomPropsProps = + PropDescriptor(_$key__testFuncProp__FunctionCustomPropsProps); static const String _$key__testProp__FunctionCustomPropsProps = 'FunctionCustomPropsProps.testProp'; + static const String _$key__testFuncProp__FunctionCustomPropsProps = + 'FunctionCustomPropsProps.testFuncProp'; static const List $props = [ - _$prop__testProp__FunctionCustomPropsProps + _$prop__testProp__FunctionCustomPropsProps, + _$prop__testFuncProp__FunctionCustomPropsProps ]; static const List $propKeys = [ - _$key__testProp__FunctionCustomPropsProps + _$key__testProp__FunctionCustomPropsProps, + _$key__testFuncProp__FunctionCustomPropsProps ]; } diff --git a/tools/analyzer_plugin/pubspec.yaml b/tools/analyzer_plugin/pubspec.yaml index 45bb998d1..c22119c0c 100644 --- a/tools/analyzer_plugin/pubspec.yaml +++ b/tools/analyzer_plugin/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: # Upon release, this should be pinned to the over_react version from ../../pubspec.yaml # so that it always resolves to the same version of over_react that the user has pulled in, # and thus has the same boilerplate parsing code that's running in the builder. - over_react: 3.12.0 + over_react: 3.12.1 meta: ^1.1.6 path: ^1.5.1 source_span: ^1.7.0