Skip to content

Commit 5facba5

Browse files
Merge pull request #262 from Workiva/backpatch_map_warnings
CPLAT-4749 Release over_react 1.32.0
2 parents f922d0c + 189d965 commit 5facba5

File tree

4 files changed

+62
-4
lines changed

4 files changed

+62
-4
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# OverReact Changelog
22

3+
## 1.32.0
4+
5+
> [Complete `1.32.0` Changeset](https://github.com/Workiva/over_react/compare/1.31.0...1.32.0)
6+
7+
* [#249] Warn consumers about props / state mutation
8+
* Directly mutating props and state is an antipattern and can cause unpredictable rendering.
9+
Avoiding this will be especially important for components to behave correctly in React 16's [concurrent mode](https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#react-16x-q2-2019-the-one-with-concurrent-mode).
10+
311
## 1.31.0
412

513
> [Complete `1.31.0` Changeset](https://github.com/Workiva/over_react/compare/1.30.2...1.31.0)

lib/src/component_declaration/component_base.dart

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ library over_react.component_declaration.component_base;
1616

1717
import 'dart:async';
1818
import 'dart:collection';
19+
import 'dart:html';
1920

2021
import 'package:meta/meta.dart';
2122
import 'package:over_react/over_react.dart';
@@ -247,7 +248,7 @@ abstract class UiComponent<TProps extends UiProps> extends react.Component imple
247248
var unwrappedProps = this.unwrappedProps;
248249
var typedProps = _typedPropsCache[unwrappedProps];
249250
if (typedProps == null) {
250-
typedProps = typedPropsFactory(unwrappedProps);
251+
typedProps = typedPropsFactory(inReactDevMode ? new _WarnOnModify(unwrappedProps, true) : unwrappedProps);
251252
_typedPropsCache[unwrappedProps] = typedProps;
252253
}
253254
return typedProps;
@@ -417,7 +418,7 @@ abstract class UiStatefulComponent<TProps extends UiProps, TState extends UiStat
417418
var unwrappedState = this.unwrappedState;
418419
var typedState = _typedStateCache[unwrappedState];
419420
if (typedState == null) {
420-
typedState = typedStateFactory(unwrappedState);
421+
typedState = typedStateFactory(inReactDevMode ? new _WarnOnModify(unwrappedState, false) : unwrappedState);
421422
_typedStateCache[unwrappedState] = typedState;
422423
}
423424
return typedState;
@@ -446,6 +447,37 @@ abstract class UiStatefulComponent<TProps extends UiProps, TState extends UiStat
446447
// ----------------------------------------------------------------------
447448
}
448449

450+
class _WarnOnModify<K, V> extends MapView<K, V> {
451+
//Used to customize warning based on whether the data is props or state
452+
bool isProps;
453+
454+
String message;
455+
456+
_WarnOnModify(Map componentData, this.isProps): super(componentData);
457+
458+
@override
459+
operator []=(K key, V value) {
460+
if (isProps) {
461+
message =
462+
'''
463+
props["$key"] was updated incorrectly. Never mutate this.props directly, as it can cause unexpected behavior;
464+
props must be updated only by passing in new values when re-rendering this component.
465+
466+
This will throw in UiComponentV2 (to be released as part of the React 16 upgrade).
467+
''';
468+
} else {
469+
message =
470+
'''
471+
state["$key"] was updated incorrectly. Never mutate this.state directly, as it can cause unexpected behavior;
472+
state must be updated only via setState.
473+
474+
This will throw in UiComponentV2 (to be released as part of the React 16 upgrade).
475+
''';
476+
}
477+
super[key] = value;
478+
ValidationUtil.warn(unindent(message));
479+
}
480+
}
449481

450482
/// A `dart.collection.MapView`-like class with strongly-typed getters/setters for React state.
451483
///

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: over_react
2-
version: 1.31.0
2+
version: 1.32.0
33
description: A library for building statically-typed React UI components using Dart.
44
homepage: https://github.com/Workiva/over_react/
55
authors:

test/over_react/component_declaration/component_base_test.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ main() {
124124
_commonNonInvokedBuilderTests(Dom.div());
125125
}, testOn: '!js');
126126

127+
test('warns against setting props directly', () {
128+
startRecordingValidationWarnings();
129+
var instance = render(TestComponent()());
130+
var component = getDartComponent(instance);
131+
var changeProps = () => component.props['id'] = 'test';
132+
changeProps();
133+
verifyValidationWarning(contains('Never mutate this.props directly'));
134+
stopRecordingValidationWarnings();
135+
});
136+
127137
group('renders a DOM component with the correct children when', () {
128138
_commonVariadicChildrenTests(Dom.div());
129139

@@ -829,7 +839,7 @@ main() {
829839

830840
setUp(() {
831841
statefulComponent = new TestStatefulComponentComponent();
832-
statefulComponent.unwrappedState = {};
842+
statefulComponent.unwrappedState = {'test': true};
833843
});
834844

835845
group('`state`', () {
@@ -856,6 +866,14 @@ main() {
856866

857867
expect(stateBeforeChange, isNot(same(stateAfterChange)));
858868
});
869+
870+
test('warns against setting state directly', () {
871+
startRecordingValidationWarnings();
872+
var changeState = () => statefulComponent.state['test'] = true;
873+
changeState();
874+
verifyValidationWarning(contains('Never mutate this.state directly'));
875+
stopRecordingValidationWarnings();
876+
});
859877
});
860878

861879
group('setter:', () {

0 commit comments

Comments
 (0)