Skip to content

Commit bc030a8

Browse files
Merge pull request #140 from jacehensley-wf/add_pure_option_to_redux_comp/dev
Add an option to make BuiltReduxUiComponent "pure"
2 parents b820881 + 9ee61cb commit bc030a8

File tree

6 files changed

+151
-20
lines changed

6 files changed

+151
-20
lines changed

lib/src/component_declaration/component_base.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ abstract class UiComponent<TProps extends UiProps> extends react.Component imple
291291
@override
292292
Future<T> getManagedDelayedFuture<T>(Duration duration, T callback()) =>
293293
_getDisposableProxy().getManagedDelayedFuture<T>(duration, callback);
294-
294+
295295
@override
296296
ManagedDisposer getManagedDisposer(Disposer disposer) => _getDisposableProxy().getManagedDisposer(disposer);
297297

@@ -309,7 +309,7 @@ abstract class UiComponent<TProps extends UiProps> extends react.Component imple
309309
{Function onError, void onDone(), bool cancelOnError}) =>
310310
_getDisposableProxy().listenToStream(
311311
stream, onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
312-
312+
313313
@override
314314
Disposable manageAndReturnDisposable(Disposable disposable) =>
315315
_getDisposableProxy().manageAndReturnDisposable(disposable);
@@ -327,7 +327,7 @@ abstract class UiComponent<TProps extends UiProps> extends react.Component imple
327327
@override
328328
void manageDisposer(Disposer disposer) =>
329329
_getDisposableProxy().manageDisposer(disposer);
330-
330+
331331
@override
332332
void manageStreamController(StreamController controller) =>
333333
_getDisposableProxy().manageStreamController(controller);

lib/src/experimental/built_redux_component.dart

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,28 @@ abstract class BuiltReduxUiComponent<
6161
@override
6262
void componentWillMount() {
6363
super.componentWillMount();
64+
_isDirty = false;
6465
_setUpSub();
6566
}
6667

6768
@mustCallSuper
6869
@override
6970
void componentWillReceiveProps(Map nextProps) {
7071
super.componentWillReceiveProps(nextProps);
71-
_tearDownSub();
72+
var tNextProps = typedPropsFactory(nextProps);
73+
74+
if (tNextProps.store != props.store) {
75+
_tearDownSub();
76+
_setUpSub(nextProps);
77+
}
7278
}
7379

7480
@mustCallSuper
7581
@override
76-
void componentWillUpdate(Map nextProps, Map nextState) {
77-
// _storeSub will only be null when props get updated, not on every re-render.
78-
if (_storeSub == null) _setUpSub(nextProps);
82+
bool shouldComponentUpdate(Map nextProps, Map nextState) {
83+
if (isPure) return _isDirty || typedPropsFactory(nextProps).store != props.store;
84+
85+
return true;
7986
}
8087

8188
@mustCallSuper
@@ -85,6 +92,20 @@ abstract class BuiltReduxUiComponent<
8592
_tearDownSub();
8693
}
8794

95+
@mustCallSuper
96+
@override
97+
void redraw([callback()]) {
98+
_isDirty = true;
99+
100+
super.redraw(() {
101+
_isDirty = false;
102+
103+
if (callback != null) callback();
104+
});
105+
}
106+
107+
bool _isDirty;
108+
88109
Substate _connectedState;
89110

90111
/// The substate values of the redux store that this component subscribes to.
@@ -145,6 +166,15 @@ abstract class BuiltReduxUiComponent<
145166
/// Related: [connectedState]
146167
Substate connect(V state);
147168

169+
/// Whether the component should be a "pure" component.
170+
///
171+
/// A "pure" component will only re-render when [connectedState] is updated or [redraw] is called directly.
172+
/// To enable this functionality, override this getter in a subclass to return `true`. When set to true it
173+
/// is not recommended to override [redraw] or [shouldComponentUpdate].
174+
///
175+
/// Related: [shouldComponentUpdate], [redraw]
176+
bool get isPure => false;
177+
148178
StreamSubscription _storeSub;
149179

150180
void _setUpSub([Map propsMap]) {

test/over_react/experimental/redux_component_test.dart

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'redux_component_test/test_reducer.dart';
1414

1515
part 'redux_component_test/default.dart';
1616
part 'redux_component_test/connect.dart';
17+
part 'redux_component_test/pure.dart';
1718

1819
void main() {
1920
ReducerBuilder<BaseState, BaseStateBuilder> baseReducerBuilder;
@@ -95,6 +96,78 @@ void main() {
9596
expect(component.numberOfRedraws, 1);
9697
});
9798

99+
group('properly redraws when isPure is true', () {
100+
test('when an action is triggered', () async {
101+
var store = new Store<BaseState, BaseStateBuilder, BaseActions>(
102+
baseReducerBuilder.build(),
103+
baseState,
104+
baseActions,
105+
);
106+
var jacket = mount<TestPureComponent>((TestPure()..store = store)());
107+
TestPureComponent component = jacket.getDartInstance();
108+
109+
store.actions.trigger1();
110+
await new Future.delayed(Duration.ZERO);
111+
expect(component.numberOfRedraws, 1);
112+
});
113+
114+
test('by not redrawing when other props change', () async {
115+
var store = new Store<BaseState, BaseStateBuilder, BaseActions>(
116+
baseReducerBuilder.build(),
117+
baseState,
118+
baseActions,
119+
);
120+
var jacket = mount<TestPureComponent>((TestPure()..store = store)());
121+
TestPureComponent component = jacket.getDartInstance();
122+
123+
expect(component.numberOfRedraws, 0);
124+
125+
jacket.rerender((TestPure()
126+
..store = store
127+
..id = 'new id'
128+
)());
129+
130+
expect(component.numberOfRedraws, 0);
131+
});
132+
133+
test('by redrawing when store changes', () async {
134+
var store = new Store<BaseState, BaseStateBuilder, BaseActions>(
135+
baseReducerBuilder.build(),
136+
baseState,
137+
baseActions,
138+
);
139+
var updatedStore = new Store<BaseState, BaseStateBuilder, BaseActions>(
140+
baseReducerBuilder.build(),
141+
new BaseState(),
142+
new BaseActions(),
143+
);
144+
var jacket = mount<TestPureComponent>((TestPure()..store = store)());
145+
TestPureComponent component = jacket.getDartInstance();
146+
147+
expect(component.numberOfRedraws, 0);
148+
149+
jacket.rerender((TestPure()..store = updatedStore)());
150+
151+
expect(component.numberOfRedraws, 1);
152+
});
153+
154+
test('when calling redraw', () {
155+
var store = new Store<BaseState, BaseStateBuilder, BaseActions>(
156+
baseReducerBuilder.build(),
157+
baseState,
158+
baseActions,
159+
);
160+
var jacket = mount<TestPureComponent>((TestPure()..store = store)());
161+
TestPureComponent component = jacket.getDartInstance();
162+
163+
expect(component.numberOfRedraws, 0);
164+
165+
component.redraw();
166+
167+
expect(component.numberOfRedraws, 1);
168+
});
169+
});
170+
98171
test('updates subscriptions when new props are passed', () async {
99172
var store = new Store<BaseState, BaseStateBuilder, BaseActions>(
100173
baseReducerBuilder.build(),
@@ -115,9 +188,11 @@ void main() {
115188

116189
jacket.rerender((TestDefault()..store = updatedStore)());
117190

191+
expect(component.numberOfRedraws, 2);
192+
118193
updatedStore.actions.trigger1();
119194
await new Future.delayed(Duration.ZERO);
120-
expect(component.numberOfRedraws, 2);
195+
expect(component.numberOfRedraws, 3);
121196
});
122197
});
123198
}

test/over_react/experimental/redux_component_test/connect.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ class TestConnectComponent
1515
int numberOfRedraws = 0;
1616

1717
@override
18-
render() => Dom.div()(connectedState);
18+
void componentDidUpdate(Map prevProps, Map prevState) {
19+
numberOfRedraws++;
20+
}
1921

2022
@override
21-
int connect(BaseState state) => state.count1;
23+
render() => Dom.div()(connectedState);
2224

2325
@override
24-
void setState(_, [callback()]) {
25-
numberOfRedraws++;
26-
if (callback != null) callback();
27-
}
26+
int connect(BaseState state) => state.count1;
2827
}

test/over_react/experimental/redux_component_test/default.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ class TestDefaultComponent extends BuiltReduxUiComponent<BaseState, BaseStateBui
1313
int numberOfRedraws = 0;
1414

1515
@override
16-
BaseState connect(BaseState state) => state;
16+
void componentDidUpdate(Map prevProps, Map prevState) {
17+
numberOfRedraws++;
18+
}
1719

1820
@override
19-
render() => Dom.div()();
21+
BaseState connect(BaseState state) => state;
2022

2123
@override
22-
void setState(_, [callback()]) {
23-
numberOfRedraws++;
24-
if (callback != null) callback();
25-
}
24+
render() => Dom.div()();
2625
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// ignore_for_file: deprecated_member_use
2+
3+
part of over_react.component_declaration.redux_component_test;
4+
5+
@Factory()
6+
UiFactory<TestPureProps> TestPure;
7+
8+
@Props()
9+
class TestPureProps extends BuiltReduxUiProps<BaseState, BaseStateBuilder, BaseActions> {}
10+
11+
@Component()
12+
class TestPureComponent extends BuiltReduxUiComponent<BaseState, BaseStateBuilder, BaseActions, TestPureProps, BaseState> {
13+
int numberOfRedraws = 0;
14+
15+
@override
16+
void componentDidUpdate(Map prevProps, Map prevState) {
17+
numberOfRedraws++;
18+
}
19+
20+
@override
21+
bool get isPure => true;
22+
23+
@override
24+
BaseState connect(BaseState state) => state;
25+
26+
@override
27+
render() => Dom.div()();
28+
}

0 commit comments

Comments
 (0)