Skip to content

Commit

Permalink
Merge pull request #380 from Workiva/v7_wip
Browse files Browse the repository at this point in the history
RM-217585 FED-1716 Release react-dart 7.0.0 (null-safety)
  • Loading branch information
rmconsole6-wk authored Nov 10, 2023
2 parents f4f729c + b27803c commit c0ccd1b
Show file tree
Hide file tree
Showing 66 changed files with 2,616 additions and 1,989 deletions.
14 changes: 11 additions & 3 deletions .github/workflows/dart_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
if: always() && steps.install.outcome == 'success'

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

- name: Analyze project source
Expand All @@ -44,12 +44,20 @@ jobs:

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

- name: Run tests (dart2js)
run: |
if [ ${{ matrix.sdk }} = '2.13.4' ]; then dart run build_runner test -- --preset dart2js-legacy; else dart run build_runner test -- --preset dart2js; fi
if [ ${{ matrix.sdk }} = '2.13.4' ]; then
dart run build_runner test --delete-conflicting-outputs --release -- --preset dart2js-legacy
else
dart run build_runner test --delete-conflicting-outputs --release -- --preset dart2js
fi
if: always() && steps.install.outcome == 'success'
timeout-minutes: 5
110 changes: 73 additions & 37 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,80 @@
## 7.0.0-wip
## 7.0.0

- Migrate to null safety

#### Deprecated API removals
- forwardRef (use forwardRef2 instead)
- memo (use memo2 instead)
- main (use htmlMain instead)
- Ref class constructors: default and `useRefInit` (use useRef/createRef instead)
- ReducerHook and StateHook class constructors (use hook functions instead).
- Remove deprecated APIs (see below)
- Minor API breakages to support null safety migration and improve typing (see below)

## Deprecated API removals
- `ReducerHook` and `StateHook` class constructors (use hook functions instead)
- `Ref` class constructors: default and `useRefInit` (use `createRef` and `useRef` instead)
- `forwardRef` (use `forwardRef2` instead)
- `main` (use `htmlMain` instead)
- `memo` (use `memo2` instead)
- APIs that have been no-ops since react-dart 6.0.0
- SyntheticEvent members `persist` and `isPersistent`
- unconvertJsEventHandler
- `SyntheticEvent` members `persist` and `isPersistent`
- `unconvertJsEventHandler`
- APIs that were never intended for public use:
- JsPropValidator
- dartInteropStatics
- ComponentStatics(2)
- createReactDartComponentClass(2)
- JsComponentConfig(2)
- ReactDartInteropStatics
- InteropContextValue
- markChildrenValidated

#### Other API breakages
- ReducerHook and StateHook have no public constructors and can no longer be extended
- Ref.fromJs is now a factory constructor, meaning the Ref class can no longer be extended
- ReactComponentFactoryProxy.call and .build return type changed from dynamic to ReactElement
- This matches the type returned from `build` for all subclasses, which is what’s returned by call, and reflects the type returned at runtime
- 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
- Needs consumer tests
- Top-level component factories are typed as ReactDomComponentFactoryProxy instead of being `dynamic`: react.div
- All PropValidatorInfo arguments are required
- Changes to public but internal code that should not affect consumers:
- ReactDartComponentInternal
- Constructor now takes a required argument, props is final
- initComponentInternal arguments are typed to reflect runtime assumptions
- ReactComponentFactoryProxy no longer `implements Function`
- This should not be a breakage, since as of Dart 2.0 inheriting from Function has had no effect

#### Potential behavior breakages
- 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).
- `ComponentStatics`, `ComponentStatics2`
- `InteropContextValue`
- `JsComponentConfig`, `JsComponentConfig2`
- `JsError`
- `JsPropValidator`
- `React.createFactory`
- `ReactDartContextInternal`
- `ReactDartInteropStatics`
- `ReactElementStore`
- `createReactDartComponentClass`, `createReactDartComponentClass2`
- `markChildValidated`
- `markChildrenValidated`

### Other API breakages

#### Miscellaneous:
- `ReducerHook`, `StateHook`, and `Ref` are now `@sealed` and may not be inherited from
- All `PropValidatorInfo` arguments are required

#### Typing improvements:
- Top-level DOM factories exported from `package:react/react.dart` (`react.div`, `react.span`, etc.) are now typed as `ReactDomComponentFactoryProxy` instead of `dynamic`
- The return types of `ReactComponentFactoryProxy` methods `call` and `build` are now `ReactElement` instead of `dynamic`
- This matches the type returned from `build` for all subclasses, which is what’s returned by call, and reflects the type returned at runtime
- 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

#### Changes very unlikely to affect consumers:
- Changes to public-but-internal APIs:
- `ReactDartComponentInternal` constructor now takes a required argument, `props` field is `final`
- `initComponentInternal` arguments are typed to reflect runtime assumptions
- `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.

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.

These fields are only uninitialized:
- for mounting component instances:
- in component class constructors (which we don't encourage)
- in component class field initializers (except for lazy `late` ones)
- in "static" lifecycle methods like `getDerivedStateFromProps` and `defaultProps`

Examples of code affected:
```dart
class FooComponent extends Component2 {
// `props` would have always been null when this is initialized, but in 7.0.0 accessing it throws.
final something = (props ?? {})['something'];
// We strongly discourage declaring Dart constructors in component classes;
// for initialization logic, use componentDidMount instead.
FooComponent() {
// `props` would have always been null here, but in 7.0.0 accessing it throws.
print(props);
}
@override
getDerivedStateFromProps(nextProps, prevState) {
// `props` would have always been null here, but in 7.0.0 accessing it throws.
print(props);
return {};
}
}
```
## [6.3.0](https://github.com/Workiva/react-dart/compare/6.2.1...6.3.0)
- [#372], [#374] Add and update deprecations in preparation for 7.0.0 release, add WIP changelog
Expand Down
3 changes: 1 addition & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ include: package:workiva_analysis_options/v2.recommended.yaml

analyzer:
strong-mode:
# TODO change to false as part of the null safety major, which avoids us having to add a lot more casts
implicit-casts: true
implicit-casts: false
errors:
must_call_super: error
comment_references: info
Expand Down
7 changes: 3 additions & 4 deletions build.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
targets:
$default:
builders:
# mockito's builder is expensive and is not needed until this package is
# migrated to null-safety. At that point, it should be scoped only to
# relevant files.
mockito:mockBuilder:
enabled: false
# Scope only to files declaring mocks, for performance.
generate_for:
- test/mockito.dart
build_web_compilers|entrypoint:
# These are globs for the entrypoints you want to compile.
generate_for:
Expand Down
4 changes: 2 additions & 2 deletions example/js_components/js_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ main() {
var IndexComponent = react.registerComponent2(() => _IndexComponent());

class _IndexComponent extends react.Component2 {
SimpleCustomComponent simpleRef;
SimpleCustomComponent? simpleRef;

@override
get initialState => {
Expand All @@ -35,7 +35,7 @@ class _IndexComponent extends react.Component2 {
setState({
'open': true,
});
print(simpleRef.getFoo());
print(simpleRef!.getFoo());
}

@override
Expand Down
14 changes: 7 additions & 7 deletions example/test/function_component_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ UseRefTestComponent(Map props) {
return react.Fragment({}, [
react.p({'key': 'urtKey1'}, ['Current Input: ${inputValue.value}, Previous Input: ${prevInputValueRef.current}']),
react.input({'key': 'urtKey2', 'ref': inputRef}),
react.button({'key': 'urtKey3', 'onClick': (_) => inputValue.set(inputRef.current.value)}, ['Update']),
react.button({'key': 'urtKey3', 'onClick': (_) => inputValue.set(inputRef.current!.value!)}, ['Update']),
]);
}

Expand Down Expand Up @@ -338,13 +338,13 @@ class FancyInputApi {
}

final FancyInput = react.forwardRef2((props, ref) {
final inputRef = useRef();
final inputRef = useRef<InputElement>();

useImperativeHandle(
ref,
() {
print('FancyInput: useImperativeHandle re-assigns ref.current');
return FancyInputApi(() => inputRef.current.focus());
return FancyInputApi(() => inputRef.current!.focus());
},

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

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

Expand Down Expand Up @@ -453,13 +453,13 @@ UseImperativeHandleTestComponent2(Map props) {
}, []),
react.button({
'key': 'button1',
'onClick': (_) => fancyCounterRef.current['increment'](),
'onClick': (_) => fancyCounterRef.current!['increment'](),
}, [
'Increment by ${diff.value}'
]),
react.button({
'key': 'button2',
'onClick': (_) => fancyCounterRef.current['decrement'](),
'onClick': (_) => fancyCounterRef.current!['decrement'](),
}, [
'Decrement by ${diff.value}'
]),
Expand Down
28 changes: 14 additions & 14 deletions example/test/react_test_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class _CheckBoxComponent extends react.Component {
var checkBoxComponent = react.registerComponent(() => _CheckBoxComponent());

class _ClockComponent extends react.Component {
Timer timer;
late Timer timer;

@override
getInitialState() => {'secondsElapsed': 0};
Expand Down Expand Up @@ -169,7 +169,7 @@ class _ListComponent extends react.Component {
@override
render() {
final items = [];
for (final item in state['items']) {
for (final item in state['items'] as List) {
items.add(react.li({'key': item}, '$item'));
}

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

class _NewContextProviderComponent extends react.Component2 {
_NewContextRefComponent componentRef;
_NewContextRefComponent? componentRef;

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

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

class _CustomException implements Exception {
int code;
String message;
String randomMessage;
final int code;
final String message;
final String randomMessage;

_CustomException(this.message, this.code) {
_CustomException(this.message, this.code) : randomMessage = _getRandomMessage(code);

static String _getRandomMessage(code) {
switch (code) {
case 1:
randomMessage = 'The code is a 1';
break;
return 'The code is a 1';
case 2:
randomMessage = 'The Code is a 2';
break;
return 'The Code is a 2';
default:
randomMessage = 'Default Error Code';
return 'Default Error Code';
}
}
}
Expand Down
17 changes: 8 additions & 9 deletions example/test/ref_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:html';

import 'package:react/react.dart' as react;
import 'package:react/react_dom.dart' as react_dom;
import 'package:react/react_client.dart';

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

Expand Down Expand Up @@ -85,36 +84,36 @@ class _ParentComponent extends react.Component {
}

// Callback refs
InputElement _inputCallbackRef;
_ChildComponent _childCallbackRef;
InputElement? _inputCallbackRef;
_ChildComponent? _childCallbackRef;
showInputCallbackRefValue(_) {
final input = react_dom.findDOMNode(_inputCallbackRef) as InputElement;
print(input.value);
}

showChildCallbackRefValue(_) {
print(_childCallbackRef.somevalue);
print(_childCallbackRef!.somevalue);
}

incrementChildCallbackRefValue(_) {
_childCallbackRef.incrementValue();
_childCallbackRef!.incrementValue();
}

// Create refs
final Ref<InputElement> _inputCreateRef = react.createRef();
final Ref<_ChildComponent> _childCreateRef = react.createRef();
final _inputCreateRef = react.createRef<InputElement>();
final _childCreateRef = react.createRef<_ChildComponent>();

showInputCreateRefValue(_) {
final input = react_dom.findDOMNode(_inputCreateRef.current) as InputElement;
print(input.value);
}

showChildCreateRefValue(_) {
print(_childCreateRef.current.somevalue);
print(_childCreateRef.current!.somevalue);
}

incrementChildCreateRefValue(_) {
_childCreateRef.current.incrementValue();
_childCreateRef.current!.incrementValue();
}

@override
Expand Down
2 changes: 1 addition & 1 deletion example/test/speed_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class _Hello extends react.Component {
@override
render() {
timeprint('rendering start');
final data = props['data'];
final data = props['data'] as List;
final children = [];
for (final elem in data) {
children.add(react.div({
Expand Down
4 changes: 2 additions & 2 deletions example/test/unmount_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ void main() {
print('What');
final mountedNode = querySelector('#content');

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

querySelector('#unmount').onClick.listen((_) => react_dom.unmountComponentAtNode(mountedNode));
querySelector('#unmount')!.onClick.listen((_) => react_dom.unmountComponentAtNode(mountedNode));
}
Loading

0 comments on commit c0ccd1b

Please sign in to comment.