Skip to content

Commit c0ccd1b

Browse files
Merge pull request #380 from Workiva/v7_wip
RM-217585 FED-1716 Release react-dart 7.0.0 (null-safety)
2 parents f4f729c + b27803c commit c0ccd1b

File tree

66 files changed

+2616
-1989
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2616
-1989
lines changed

.github/workflows/dart_ci.yml

+11-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
if: always() && steps.install.outcome == 'success'
3636

3737
- name: Verify formatting
38-
run: dart format --output=none --line-length=120 --set-exit-if-changed .
38+
run: dart run dart_dev format --check
3939
if: ${{ matrix.sdk == '2.18.7' }}
4040

4141
- name: Analyze project source
@@ -44,12 +44,20 @@ jobs:
4444

4545
- name: Run tests (DDC)
4646
run: |
47-
if [ ${{ matrix.sdk }} = '2.13.4' ]; then dart run build_runner test -- --preset dartdevc-legacy; else dart run build_runner test -- --preset dartdevc; fi
47+
if [ ${{ matrix.sdk }} = '2.13.4' ]; then
48+
dart run build_runner test --delete-conflicting-outputs -- --preset dartdevc-legacy
49+
else
50+
dart run build_runner test --delete-conflicting-outputs -- --preset dartdevc
51+
fi
4852
if: always() && steps.install.outcome == 'success'
4953
timeout-minutes: 5
5054

5155
- name: Run tests (dart2js)
5256
run: |
53-
if [ ${{ matrix.sdk }} = '2.13.4' ]; then dart run build_runner test -- --preset dart2js-legacy; else dart run build_runner test -- --preset dart2js; fi
57+
if [ ${{ matrix.sdk }} = '2.13.4' ]; then
58+
dart run build_runner test --delete-conflicting-outputs --release -- --preset dart2js-legacy
59+
else
60+
dart run build_runner test --delete-conflicting-outputs --release -- --preset dart2js
61+
fi
5462
if: always() && steps.install.outcome == 'success'
5563
timeout-minutes: 5

CHANGELOG.md

+73-37
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,80 @@
1-
## 7.0.0-wip
1+
## 7.0.0
22

33
- Migrate to null safety
4-
5-
#### Deprecated API removals
6-
- forwardRef (use forwardRef2 instead)
7-
- memo (use memo2 instead)
8-
- main (use htmlMain instead)
9-
- Ref class constructors: default and `useRefInit` (use useRef/createRef instead)
10-
- ReducerHook and StateHook class constructors (use hook functions instead).
4+
- Remove deprecated APIs (see below)
5+
- Minor API breakages to support null safety migration and improve typing (see below)
6+
7+
## Deprecated API removals
8+
- `ReducerHook` and `StateHook` class constructors (use hook functions instead)
9+
- `Ref` class constructors: default and `useRefInit` (use `createRef` and `useRef` instead)
10+
- `forwardRef` (use `forwardRef2` instead)
11+
- `main` (use `htmlMain` instead)
12+
- `memo` (use `memo2` instead)
1113
- APIs that have been no-ops since react-dart 6.0.0
12-
- SyntheticEvent members `persist` and `isPersistent`
13-
- unconvertJsEventHandler
14+
- `SyntheticEvent` members `persist` and `isPersistent`
15+
- `unconvertJsEventHandler`
1416
- APIs that were never intended for public use:
15-
- JsPropValidator
16-
- dartInteropStatics
17-
- ComponentStatics(2)
18-
- createReactDartComponentClass(2)
19-
- JsComponentConfig(2)
20-
- ReactDartInteropStatics
21-
- InteropContextValue
22-
- markChildrenValidated
23-
24-
#### Other API breakages
25-
- ReducerHook and StateHook have no public constructors and can no longer be extended
26-
- Ref.fromJs is now a factory constructor, meaning the Ref class can no longer be extended
27-
- ReactComponentFactoryProxy.call and .build return type changed from dynamic to ReactElement
28-
- This matches the type returned from `build` for all subclasses, which is what’s returned by call, and reflects the type returned at runtime
29-
- Has potential to cause some static analysis issues, but for the most part should not affect anything since ReactElement is typically treated as an opaque type
30-
- Needs consumer tests
31-
- Top-level component factories are typed as ReactDomComponentFactoryProxy instead of being `dynamic`: react.div
32-
- All PropValidatorInfo arguments are required
33-
- Changes to public but internal code that should not affect consumers:
34-
- ReactDartComponentInternal
35-
- Constructor now takes a required argument, props is final
36-
- initComponentInternal arguments are typed to reflect runtime assumptions
37-
- ReactComponentFactoryProxy no longer `implements Function`
38-
- This should not be a breakage, since as of Dart 2.0 inheriting from Function has had no effect
39-
40-
#### Potential behavior breakages
41-
- Component and Component2 members `props`/`state`/`jsThis` are late, will now throw instead of being null if accessed before initialized (e.g., in a constructor, final class field, or static lifecycle method).
17+
- `ComponentStatics`, `ComponentStatics2`
18+
- `InteropContextValue`
19+
- `JsComponentConfig`, `JsComponentConfig2`
20+
- `JsError`
21+
- `JsPropValidator`
22+
- `React.createFactory`
23+
- `ReactDartContextInternal`
24+
- `ReactDartInteropStatics`
25+
- `ReactElementStore`
26+
- `createReactDartComponentClass`, `createReactDartComponentClass2`
27+
- `markChildValidated`
28+
- `markChildrenValidated`
29+
30+
### Other API breakages
31+
32+
#### Miscellaneous:
33+
- `ReducerHook`, `StateHook`, and `Ref` are now `@sealed` and may not be inherited from
34+
- All `PropValidatorInfo` arguments are required
35+
36+
#### Typing improvements:
37+
- Top-level DOM factories exported from `package:react/react.dart` (`react.div`, `react.span`, etc.) are now typed as `ReactDomComponentFactoryProxy` instead of `dynamic`
38+
- The return types of `ReactComponentFactoryProxy` methods `call` and `build` are now `ReactElement` instead of `dynamic`
39+
- This matches the type returned from `build` for all subclasses, which is what’s returned by call, and reflects the type returned at runtime
40+
- Has potential to cause some static analysis issues, but for the most part should not affect anything since `ReactElement` is typically treated as an opaque type
41+
42+
#### Changes very unlikely to affect consumers:
43+
- Changes to public-but-internal APIs:
44+
- `ReactDartComponentInternal` constructor now takes a required argument, `props` field is `final`
45+
- `initComponentInternal` arguments are typed to reflect runtime assumptions
46+
- `Component` and `Component2` members `props`/`state`/`jsThis` are now [late](https://dart.dev/language/variables#late-variables), and will now throw instead of being null if accessed before initialized.
47+
48+
It should be very uncommon for components to be affected by this change, and any affected components are likely doing something wrong to begin with.
49+
50+
These fields are only uninitialized:
51+
- for mounting component instances:
52+
- in component class constructors (which we don't encourage)
53+
- in component class field initializers (except for lazy `late` ones)
54+
- in "static" lifecycle methods like `getDerivedStateFromProps` and `defaultProps`
55+
56+
Examples of code affected:
57+
```dart
58+
class FooComponent extends Component2 {
59+
// `props` would have always been null when this is initialized, but in 7.0.0 accessing it throws.
60+
final something = (props ?? {})['something'];
61+
62+
// We strongly discourage declaring Dart constructors in component classes;
63+
// for initialization logic, use componentDidMount instead.
64+
FooComponent() {
65+
// `props` would have always been null here, but in 7.0.0 accessing it throws.
66+
print(props);
67+
}
68+
69+
@override
70+
getDerivedStateFromProps(nextProps, prevState) {
71+
// `props` would have always been null here, but in 7.0.0 accessing it throws.
72+
print(props);
73+
return {};
74+
}
75+
}
76+
```
77+
4278
4379
## [6.3.0](https://github.com/Workiva/react-dart/compare/6.2.1...6.3.0)
4480
- [#372], [#374] Add and update deprecations in preparation for 7.0.0 release, add WIP changelog

analysis_options.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ include: package:workiva_analysis_options/v2.recommended.yaml
22

33
analyzer:
44
strong-mode:
5-
# TODO change to false as part of the null safety major, which avoids us having to add a lot more casts
6-
implicit-casts: true
5+
implicit-casts: false
76
errors:
87
must_call_super: error
98
comment_references: info

build.yaml

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
targets:
22
$default:
33
builders:
4-
# mockito's builder is expensive and is not needed until this package is
5-
# migrated to null-safety. At that point, it should be scoped only to
6-
# relevant files.
74
mockito:mockBuilder:
8-
enabled: false
5+
# Scope only to files declaring mocks, for performance.
6+
generate_for:
7+
- test/mockito.dart
98
build_web_compilers|entrypoint:
109
# These are globs for the entrypoints you want to compile.
1110
generate_for:

example/js_components/js_components.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ main() {
1818
var IndexComponent = react.registerComponent2(() => _IndexComponent());
1919

2020
class _IndexComponent extends react.Component2 {
21-
SimpleCustomComponent simpleRef;
21+
SimpleCustomComponent? simpleRef;
2222

2323
@override
2424
get initialState => {
@@ -35,7 +35,7 @@ class _IndexComponent extends react.Component2 {
3535
setState({
3636
'open': true,
3737
});
38-
print(simpleRef.getFoo());
38+
print(simpleRef!.getFoo());
3939
}
4040

4141
@override

example/test/function_component_test.dart

+7-7
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ UseRefTestComponent(Map props) {
240240
return react.Fragment({}, [
241241
react.p({'key': 'urtKey1'}, ['Current Input: ${inputValue.value}, Previous Input: ${prevInputValueRef.current}']),
242242
react.input({'key': 'urtKey2', 'ref': inputRef}),
243-
react.button({'key': 'urtKey3', 'onClick': (_) => inputValue.set(inputRef.current.value)}, ['Update']),
243+
react.button({'key': 'urtKey3', 'onClick': (_) => inputValue.set(inputRef.current!.value!)}, ['Update']),
244244
]);
245245
}
246246

@@ -338,13 +338,13 @@ class FancyInputApi {
338338
}
339339

340340
final FancyInput = react.forwardRef2((props, ref) {
341-
final inputRef = useRef();
341+
final inputRef = useRef<InputElement>();
342342

343343
useImperativeHandle(
344344
ref,
345345
() {
346346
print('FancyInput: useImperativeHandle re-assigns ref.current');
347-
return FancyInputApi(() => inputRef.current.focus());
347+
return FancyInputApi(() => inputRef.current!.focus());
348348
},
349349

350350
/// Because the return value of createHandle never changes, it is not necessary for ref.current
@@ -377,14 +377,14 @@ UseImperativeHandleTestComponent(Map props) {
377377
if (!RegExp(r'^[a-zA-Z]+$').hasMatch(city.value)) {
378378
message.set('Invalid form!');
379379
error.set('city');
380-
cityRef.current.focus();
380+
cityRef.current!.focus();
381381
return;
382382
}
383383

384384
if (!RegExp(r'^[a-zA-Z]+$').hasMatch(state.value)) {
385385
message.set('Invalid form!');
386386
error.set('state');
387-
stateRef.current.focus();
387+
stateRef.current!.focus();
388388
return;
389389
}
390390

@@ -453,13 +453,13 @@ UseImperativeHandleTestComponent2(Map props) {
453453
}, []),
454454
react.button({
455455
'key': 'button1',
456-
'onClick': (_) => fancyCounterRef.current['increment'](),
456+
'onClick': (_) => fancyCounterRef.current!['increment'](),
457457
}, [
458458
'Increment by ${diff.value}'
459459
]),
460460
react.button({
461461
'key': 'button2',
462-
'onClick': (_) => fancyCounterRef.current['decrement'](),
462+
'onClick': (_) => fancyCounterRef.current!['decrement'](),
463463
}, [
464464
'Decrement by ${diff.value}'
465465
]),

example/test/react_test_components.dart

+14-14
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class _CheckBoxComponent extends react.Component {
8787
var checkBoxComponent = react.registerComponent(() => _CheckBoxComponent());
8888

8989
class _ClockComponent extends react.Component {
90-
Timer timer;
90+
late Timer timer;
9191

9292
@override
9393
getInitialState() => {'secondsElapsed': 0};
@@ -169,7 +169,7 @@ class _ListComponent extends react.Component {
169169
@override
170170
render() {
171171
final items = [];
172-
for (final item in state['items']) {
172+
for (final item in state['items'] as List) {
173173
items.add(react.li({'key': item}, '$item'));
174174
}
175175

@@ -297,7 +297,7 @@ int calculateChangedBits(currentValue, nextValue) {
297297
var TestNewContext = react.createContext<Map>({'renderCount': 0}, calculateChangedBits);
298298

299299
class _NewContextProviderComponent extends react.Component2 {
300-
_NewContextRefComponent componentRef;
300+
_NewContextRefComponent? componentRef;
301301

302302
@override
303303
get initialState => {'renderCount': 0, 'complexMap': false};
@@ -448,7 +448,7 @@ class _Component2TestComponent extends react.Component2 with react.TypedSnapshot
448448
}
449449

450450
@override
451-
componentDidUpdate(prevProps, prevState, [String snapshot]) {
451+
componentDidUpdate(prevProps, prevState, [String? snapshot]) {
452452
if (snapshot != null) {
453453
print('Updated DOM and $snapshot');
454454
return;
@@ -473,7 +473,7 @@ class _Component2TestComponent extends react.Component2 with react.TypedSnapshot
473473
// Used to generate unique keys even when the list contains duplicate items
474474
final itemCounts = <dynamic, int>{};
475475
final items = [];
476-
for (final item in state['items']) {
476+
for (final item in state['items'] as List) {
477477
final count = itemCounts[item] = (itemCounts[item] ?? 0) + 1;
478478
items.add(react.li({'key': 'c2-$item-$count'}, '$item'));
479479
}
@@ -519,20 +519,20 @@ class _ErrorComponent extends react.Component2 {
519519
var ErrorComponent = react.registerComponent(() => _ErrorComponent());
520520

521521
class _CustomException implements Exception {
522-
int code;
523-
String message;
524-
String randomMessage;
522+
final int code;
523+
final String message;
524+
final String randomMessage;
525525

526-
_CustomException(this.message, this.code) {
526+
_CustomException(this.message, this.code) : randomMessage = _getRandomMessage(code);
527+
528+
static String _getRandomMessage(code) {
527529
switch (code) {
528530
case 1:
529-
randomMessage = 'The code is a 1';
530-
break;
531+
return 'The code is a 1';
531532
case 2:
532-
randomMessage = 'The Code is a 2';
533-
break;
533+
return 'The Code is a 2';
534534
default:
535-
randomMessage = 'Default Error Code';
535+
return 'Default Error Code';
536536
}
537537
}
538538
}

example/test/ref_test.dart

+8-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'dart:html';
33

44
import 'package:react/react.dart' as react;
55
import 'package:react/react_dom.dart' as react_dom;
6-
import 'package:react/react_client.dart';
76

87
var ChildComponent = react.registerComponent(() => _ChildComponent());
98

@@ -85,36 +84,36 @@ class _ParentComponent extends react.Component {
8584
}
8685

8786
// Callback refs
88-
InputElement _inputCallbackRef;
89-
_ChildComponent _childCallbackRef;
87+
InputElement? _inputCallbackRef;
88+
_ChildComponent? _childCallbackRef;
9089
showInputCallbackRefValue(_) {
9190
final input = react_dom.findDOMNode(_inputCallbackRef) as InputElement;
9291
print(input.value);
9392
}
9493

9594
showChildCallbackRefValue(_) {
96-
print(_childCallbackRef.somevalue);
95+
print(_childCallbackRef!.somevalue);
9796
}
9897

9998
incrementChildCallbackRefValue(_) {
100-
_childCallbackRef.incrementValue();
99+
_childCallbackRef!.incrementValue();
101100
}
102101

103102
// Create refs
104-
final Ref<InputElement> _inputCreateRef = react.createRef();
105-
final Ref<_ChildComponent> _childCreateRef = react.createRef();
103+
final _inputCreateRef = react.createRef<InputElement>();
104+
final _childCreateRef = react.createRef<_ChildComponent>();
106105

107106
showInputCreateRefValue(_) {
108107
final input = react_dom.findDOMNode(_inputCreateRef.current) as InputElement;
109108
print(input.value);
110109
}
111110

112111
showChildCreateRefValue(_) {
113-
print(_childCreateRef.current.somevalue);
112+
print(_childCreateRef.current!.somevalue);
114113
}
115114

116115
incrementChildCreateRefValue(_) {
117-
_childCreateRef.current.incrementValue();
116+
_childCreateRef.current!.incrementValue();
118117
}
119118

120119
@override

example/test/speed_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class _Hello extends react.Component {
5353
@override
5454
render() {
5555
timeprint('rendering start');
56-
final data = props['data'];
56+
final data = props['data'] as List;
5757
final children = [];
5858
for (final elem in data) {
5959
children.add(react.div({

example/test/unmount_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ void main() {
2323
print('What');
2424
final mountedNode = querySelector('#content');
2525

26-
querySelector('#mount').onClick.listen((_) => react_dom.render(simpleComponent({}), mountedNode));
26+
querySelector('#mount')!.onClick.listen((_) => react_dom.render(simpleComponent({}), mountedNode));
2727

28-
querySelector('#unmount').onClick.listen((_) => react_dom.unmountComponentAtNode(mountedNode));
28+
querySelector('#unmount')!.onClick.listen((_) => react_dom.unmountComponentAtNode(mountedNode));
2929
}

0 commit comments

Comments
 (0)