diff --git a/.github/workflows/dart_ci.yml b/.github/workflows/dart_ci.yml index f5ab2439..7abd5a1f 100644 --- a/.github/workflows/dart_ci.yml +++ b/.github/workflows/dart_ci.yml @@ -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 @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7057b9de..3e01341b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,7 @@ - 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 +- `ReducerHook`, `StateHook`, and `Ref` are now `@sealed` and may not be inherited from - 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 @@ -37,8 +36,39 @@ - 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). +#### Behavior breakages unlikely to cause issues +- 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.2.1](https://github.com/Workiva/react-dart/compare/6.2.0...6.2.1) - [#366] Fix lints and eliminate most implicit casts diff --git a/analysis_options.yaml b/analysis_options.yaml index ed7c50a6..c0e3b0c6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -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 diff --git a/build.yaml b/build.yaml index 1b101855..5bee8d28 100644 --- a/build.yaml +++ b/build.yaml @@ -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: diff --git a/example/js_components/js_components.dart b/example/js_components/js_components.dart index 08132513..f114220a 100644 --- a/example/js_components/js_components.dart +++ b/example/js_components/js_components.dart @@ -18,7 +18,7 @@ main() { var IndexComponent = react.registerComponent2(() => _IndexComponent()); class _IndexComponent extends react.Component2 { - SimpleCustomComponent simpleRef; + SimpleCustomComponent? simpleRef; @override get initialState => { @@ -35,7 +35,7 @@ class _IndexComponent extends react.Component2 { setState({ 'open': true, }); - print(simpleRef.getFoo()); + print(simpleRef!.getFoo()); } @override diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 1435a583..618b2ed0 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -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']), ]); } @@ -338,13 +338,13 @@ class FancyInputApi { } final FancyInput = react.forwardRef2((props, ref) { - final inputRef = useRef(); + final inputRef = useRef(); 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 @@ -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; } @@ -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}' ]), diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index 6a0db285..84c179bd 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -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}; @@ -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')); } @@ -297,7 +297,7 @@ int calculateChangedBits(currentValue, nextValue) { var TestNewContext = react.createContext({'renderCount': 0}, calculateChangedBits); class _NewContextProviderComponent extends react.Component2 { - _NewContextRefComponent componentRef; + _NewContextRefComponent? componentRef; @override get initialState => {'renderCount': 0, 'complexMap': false}; @@ -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; @@ -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 = {}; 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')); } @@ -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'; } } } diff --git a/example/test/ref_test.dart b/example/test/ref_test.dart index 248dd73f..118451e7 100644 --- a/example/test/ref_test.dart +++ b/example/test/ref_test.dart @@ -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()); @@ -85,24 +84,24 @@ 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 _inputCreateRef = react.createRef(); - final Ref<_ChildComponent> _childCreateRef = react.createRef(); + final _inputCreateRef = react.createRef(); + final _childCreateRef = react.createRef<_ChildComponent>(); showInputCreateRefValue(_) { final input = react_dom.findDOMNode(_inputCreateRef.current) as InputElement; @@ -110,11 +109,11 @@ class _ParentComponent extends react.Component { } showChildCreateRefValue(_) { - print(_childCreateRef.current.somevalue); + print(_childCreateRef.current!.somevalue); } incrementChildCreateRefValue(_) { - _childCreateRef.current.incrementValue(); + _childCreateRef.current!.incrementValue(); } @override diff --git a/example/test/speed_test.dart b/example/test/speed_test.dart index 057e5a93..4a32f6dd 100644 --- a/example/test/speed_test.dart +++ b/example/test/speed_test.dart @@ -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({ diff --git a/example/test/unmount_test.dart b/example/test/unmount_test.dart index 2cb484a2..18b08db8 100644 --- a/example/test/unmount_test.dart +++ b/example/test/unmount_test.dart @@ -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)); } diff --git a/lib/hooks.dart b/lib/hooks.dart index 6e9c6f47..73cb4496 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -2,9 +2,19 @@ library hooks; import 'package:js/js.dart'; +import 'package:meta/meta.dart'; import 'package:react/react.dart'; import 'package:react/react_client/react_interop.dart'; +/// A setter function returned as the second item in the result of a JS useState call, +/// which accepts either a value or an updater function. +/// +/// If Dart had type unions, the typing would be: `void Function(T|(T Function(T)) valueOrUpdater)` +/// +/// We could make this generic, but the generic value wouldn't be used at all since the argument +/// can only be expressed as `dynamic`. +typedef _JsStateHookSetter/**/ = void Function(dynamic /*T|(T Function(T))*/ valueOrUpdater); + /// The return value of [useState]. /// /// The current value of the state is available via [value] and @@ -16,30 +26,15 @@ import 'package:react/react_client/react_interop.dart'; /// > * Only call Hooks from inside a [DartFunctionComponent]. /// /// Learn more: . +@sealed class StateHook { /// The first item of the pair returned by [React.useState]. - T _value; + final T _value; /// The second item in the pair returned by [React.useState]. - void Function(dynamic) _setValue; + final _JsStateHookSetter _setValue; - @Deprecated('Use useState instead. Will be removed in 7.0.0.') - StateHook(T initialValue) { - final result = React.useState(initialValue); - _value = result[0] as T; - _setValue = result[1] as void Function(dynamic); - } - - /// Constructor for [useStateLazy], calls lazy version of [React.useState] to - /// initialize [_value] to the return value of [init]. - /// - /// See: . - @Deprecated('Use useStateLazy instead. Will be removed in 7.0.0.') - StateHook.lazy(T Function() init) { - final result = React.useState(allowInterop(init)); - _value = result[0] as T; - _setValue = result[1] as void Function(dynamic); - } + StateHook._(this._value, this._setValue); /// The current value of the state. /// @@ -79,7 +74,10 @@ class StateHook { /// ``` /// /// Learn more: . -StateHook useState(T initialValue) => StateHook(initialValue); +StateHook useState(T initialValue) { + final result = React.useState(initialValue); + return StateHook._(result[0] as T, result[1] as _JsStateHookSetter); +} /// Adds local state to a [DartFunctionComponent] /// by returning a [StateHook] with [StateHook.value] initialized to the return value of [init]. @@ -102,7 +100,10 @@ StateHook useState(T initialValue) => StateHook(initialValue); /// ``` /// /// Learn more: . -StateHook useStateLazy(T Function() init) => StateHook.lazy(init); +StateHook useStateLazy(T Function() init) { + final result = React.useState(allowInterop(init)); + return StateHook._(result[0] as T, result[1] as _JsStateHookSetter); +} /// Runs [sideEffect] after every completed render of a [DartFunctionComponent]. /// @@ -142,7 +143,7 @@ StateHook useStateLazy(T Function() init) => StateHook.lazy(init); /// ``` /// /// See: . -void useEffect(dynamic Function() sideEffect, [List dependencies]) { +void useEffect(dynamic Function() sideEffect, [List? dependencies]) { final wrappedSideEffect = allowInterop(() { final result = sideEffect(); if (result is Function) { @@ -166,31 +167,15 @@ void useEffect(dynamic Function() sideEffect, [List dependencies]) { /// > * Only call Hooks from inside a [DartFunctionComponent]. /// /// Learn more: . +@sealed class ReducerHook { /// The first item of the pair returned by [React.useReducer]. - TState _state; + final TState _state; /// The second item in the pair returned by [React.useReducer]. - void Function(TAction) _dispatch; + final void Function(TAction) _dispatch; - @Deprecated('Use useReducer instead. Will be removed in 7.0.0.') - ReducerHook(TState Function(TState state, TAction action) reducer, TState initialState) { - final result = React.useReducer(allowInterop(reducer), initialState); - _state = result[0] as TState; - _dispatch = result[1] as void Function(TAction); - } - - /// Constructor for [useReducerLazy], calls lazy version of [React.useReducer] to - /// initialize [_state] to the return value of [init(initialArg)]. - /// - /// See: . - @Deprecated('Use useReducerLazy instead. Will be removed in 7.0.0.') - ReducerHook.lazy( - TState Function(TState state, TAction action) reducer, TInit initialArg, TState Function(TInit) init) { - final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); - _state = result[0] as TState; - _dispatch = result[1] as void Function(TAction); - } + ReducerHook._(this._state, this._dispatch); /// The current state map of the component. /// @@ -242,8 +227,10 @@ class ReducerHook { /// /// Learn more: . ReducerHook useReducer( - TState Function(TState state, TAction action) reducer, TState initialState) => - ReducerHook(reducer, initialState); + TState Function(TState state, TAction action) reducer, TState initialState) { + final result = React.useReducer(allowInterop(reducer), initialState); + return ReducerHook._(result[0] as TState, result[1] as void Function(TAction)); +} /// Initializes state of a [DartFunctionComponent] to `init(initialArg)` and creates `dispatch` method. /// @@ -296,8 +283,10 @@ ReducerHook useReducer( /// /// Learn more: . ReducerHook useReducerLazy( - TState Function(TState state, TAction action) reducer, TInit initialArg, TState Function(TInit) init) => - ReducerHook.lazy(reducer, initialArg, init); + TState Function(TState state, TAction action) reducer, TInit initialArg, TState Function(TInit) init) { + final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); + return ReducerHook._(result[0] as TState, result[1] as void Function(TAction)); +} /// Returns a memoized version of [callback] that only changes if one of the [dependencies] has changed. /// @@ -362,7 +351,9 @@ T useCallback(T callback, List dependencies) => /// Learn more: . T useContext(Context context) => ContextHelpers.unjsifyNewContext(React.useContext(context.jsThis)) as T; -/// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue]. +/// Returns an empty mutable [Ref] object. +/// +/// To initialize a ref with a value, use [useRefInit] instead. /// /// Changes to the [Ref.current] property do not cause the containing [DartFunctionComponent] to re-render. /// @@ -396,12 +387,11 @@ T useContext(Context context) => ContextHelpers.unjsifyNewContext(React.us /// ``` /// /// Learn more: . -Ref useRef([ - // This will eventually be deprecated, but not just yet. - // @Deprecated('Use `useRefInit` instead to create refs with initial values.' - // ' Since the argument to useRefInit is required, it can be used to create a Ref that holds a non-nullable type,' - // ' whereas this function can only create Refs with nullable type arguments.') - T initialValue, +Ref useRef([ + @Deprecated('Use `useRefInit` instead to create refs with initial values.' + ' Since the argument to useRefInit is required, it can be used to create a Ref that holds a non-nullable type,' + ' whereas this function can only create Refs with nullable type arguments.') + T? initialValue, ]) => useRefInit(initialValue); @@ -421,7 +411,7 @@ Ref useRef([ /// /// ```dart /// UseRefTestComponent(Map props) { -/// final countRef = useRefInit(0); +/// final countRef = useRefInit(1); /// /// handleClick([_]) { /// ref.current = ref.current + 1; @@ -435,7 +425,7 @@ Ref useRef([ /// ``` /// /// Learn more: . -Ref useRefInit(T initialValue) => Ref.useRefInit(initialValue); +Ref useRefInit(T initialValue) => Ref.fromJs(React.useRef(initialValue)); /// Returns a memoized version of the return value of [createFunction]. /// @@ -468,7 +458,7 @@ Ref useRefInit(T initialValue) => Ref.useRefInit(initialValue); /// ``` /// /// Learn more: . -T useMemo(T Function() createFunction, [List dependencies]) => +T useMemo(T Function() createFunction, [List? dependencies]) => React.useMemo(allowInterop(createFunction), dependencies) as T; /// Runs [sideEffect] synchronously after a [DartFunctionComponent] renders, but before the screen is updated. @@ -504,7 +494,7 @@ T useMemo(T Function() createFunction, [List dependencies]) => /// ``` /// /// Learn more: . -void useLayoutEffect(dynamic Function() sideEffect, [List dependencies]) { +void useLayoutEffect(dynamic Function() sideEffect, [List? dependencies]) { final wrappedSideEffect = allowInterop(() { final result = sideEffect(); if (result is Function) { @@ -572,7 +562,7 @@ void useLayoutEffect(dynamic Function() sideEffect, [List dependencies]) /// ``` /// /// Learn more: . -void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List dependencies]) => +void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List? dependencies]) => // ref will be a JsRef in forwardRef2, or a Ref in forwardRef. (Or null if no ref is provided) // // For some reason the ref argument to React.forwardRef is usually a JsRef object no matter the input ref type, @@ -653,7 +643,7 @@ void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List. -dynamic useDebugValue(T value, [dynamic Function(T) format]) { +dynamic useDebugValue(T value, [dynamic Function(T)? format]) { if (format == null) { return React.useDebugValue(value); } diff --git a/lib/react.dart b/lib/react.dart index 5f19b77e..3981f9ee 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -16,18 +16,16 @@ import 'package:react/react_client.dart'; import 'package:react/react_client/react_interop.dart'; import 'package:react/src/context.dart'; import 'package:react/src/react_client/component_registration.dart' as registration_utils; -import 'package:react/src/react_client/private_utils.dart' show validateJsApiThenReturn; +import 'package:react/src/react_client/private_utils.dart' show validateJsApi, validateJsApiThenReturn; export 'package:react/src/context.dart'; export 'package:react/src/prop_validator.dart'; export 'package:react/src/react_client/event_helpers.dart'; -export 'package:react/react_client/react_interop.dart' show forwardRef, forwardRef2, createRef, memo, memo2; +export 'package:react/react_client/react_interop.dart' show forwardRef2, createRef, memo, memo2; export 'package:react/src/react_client/synthetic_event_wrappers.dart' hide NonNativeDataTransfer; export 'package:react/src/react_client/synthetic_data_transfer.dart' show SyntheticDataTransfer; export 'package:react/src/react_client/event_helpers.dart'; -typedef PropValidator = Error Function(TProps props, PropValidatorInfo info); - /// A React component declared using a function that takes in [props] and returns rendered output. /// /// See . @@ -52,11 +50,11 @@ typedef ComponentRegistrar = ReactComponentFactoryProxy Function(ComponentFactor typedef ComponentRegistrar2 = ReactDartComponentFactoryProxy2 Function( ComponentFactory componentFactory, { Iterable skipMethods, - Component2BridgeFactory bridgeFactory, + Component2BridgeFactory? bridgeFactory, }); typedef FunctionComponentRegistrar = ReactDartFunctionComponentFactoryProxy - Function(DartFunctionComponent componentFactory, {String displayName}); + Function(DartFunctionComponent componentFactory, {String? displayName}); /// Fragment component that allows the wrapping of children without the necessity of using /// an element that adds an additional layer to the DOM (div, span, etc). @@ -103,6 +101,22 @@ var Suspense = ReactJsComponentFactoryProxy(React.Suspense); /// See: var StrictMode = ReactJsComponentFactoryProxy(React.StrictMode); +// ------------------------------------------------------------------------------------------------------------------- +// [1] While these fields are always initialized upon mount immediately after the class is instantiated, +// since they're not passed into a Dart constructor, they can't be initialized during instantiation, +// forcing us to make them either `late` or nullable. +// +// Since we want them to be non-nullable, we'll opt for `late`. +// +// These fields only haven't been initialized: +// - 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` +// +// So, this shouldn't pose a problem for consumers. +// ------------------------------------------------------------------------------------------------------------------- + /// Top-level ReactJS [Component class](https://reactjs.org/docs/react-component.html) /// which provides the [ReactJS Component API](https://reactjs.org/docs/react-component.html#reference) /// @@ -113,7 +127,7 @@ var StrictMode = ReactJsComponentFactoryProxy(React.StrictMode); 'The Component base class only supports unsafe lifecycle methods, which React JS will remove support for in a future major version.' ' Migrate components to Component2, which only supports safe lifecycle methods.') abstract class Component { - Map _context; + Map? _context; /// A private field that backs [props], which is exposed via getter/setter so /// it can be overridden in strong mode. @@ -122,7 +136,7 @@ abstract class Component { /// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452). /// /// TODO: Switch back to a plain field once this issue is fixed. - Map _props; + late Map _props; // [1] /// A private field that backs [state], which is exposed via getter/setter so /// it can be overridden in strong mode. @@ -140,7 +154,7 @@ abstract class Component { /// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452). /// /// TODO: Switch back to a plain field once this issue is fixed. - RefMethod _ref; + late RefMethod _ref; // [1] /// The React context map of this component, passed down from its ancestors' [getChildContext] value. /// @@ -192,15 +206,13 @@ abstract class Component { 'Only supported in the deprecated Component, and not Component2. Use createRef or a ref callback instead.') set ref(RefMethod value) => _ref = value; - dynamic _jsRedraw; + late void Function() _jsRedraw; // [1] - dynamic _jsThis; + late dynamic _jsThis; // [1] - // ignore: prefer_final_fields - List _setStateCallbacks = []; + final List _setStateCallbacks = []; - // ignore: prefer_final_fields - List _transactionalSetStateCallbacks = []; + final List _transactionalSetStateCallbacks = []; /// The List of callbacks to be called after the component has been updated from a call to [setState]. List get setStateCallbacks => _setStateCallbacks; @@ -214,31 +226,30 @@ abstract class Component { /// Allows the [ReactJS `displayName` property](https://reactjs.org/docs/react-component.html#displayname) /// to be set for debugging purposes. - String get displayName => runtimeType.toString(); + // This return type is nullable since Component2's override will return null in certain cases. + String? get displayName => runtimeType.toString(); + + static dynamic _defaultRef(String _) => null; - initComponentInternal(props, _jsRedraw, [RefMethod ref, _jsThis, context]) { + initComponentInternal(Map props, void Function() _jsRedraw, [RefMethod? ref, dynamic _jsThis, Map? context]) { this._jsRedraw = _jsRedraw; - this.ref = ref; + this.ref = ref ?? _defaultRef; this._jsThis = _jsThis; _initContext(context); _initProps(props); } /// Initializes context - _initContext(context) { - /// [context]s typing was loosened from Map to dynamic to support the new context API in [Component2] + _initContext(Map? context) { + /// [context]'s and [nextContext]'ss typings were loosened from Map to dynamic to support the new context API in [Component2] /// which extends from [Component]. Only "legacy" context APIs are supported in [Component] - which means /// it will still be expected to be a Map. - this.context = Map.from(context as Map ?? const {}); - - /// [nextContext]s typing was loosened from Map to dynamic to support the new context API in [Component2] - /// which extends from [Component]. Only "legacy" context APIs are supported in [Component] - which means - /// it will still be expected to be a Map. - nextContext = Map.from(this.context as Map ?? const {}); + this.context = {...?context}; + nextContext = {...?context}; } - _initProps(props) { - this.props = Map.from(props as Map); + _initProps(Map props) { + this.props = Map.from(props); nextProps = this.props; } @@ -259,12 +270,12 @@ abstract class Component { /// > in ReactJS 16. @Deprecated('This legacy, unstable context API is only supported in the deprecated Component, and not Component2.' ' Instead, use Component2.context, Context.Consumer, or useContext.') - Map nextContext; + Map? nextContext; /// Private reference to the value of [state] for the upcoming render cycle. /// /// Useful for ReactJS lifecycle methods [shouldComponentUpdate], [componentWillUpdate] and [componentDidUpdate]. - Map _nextState; + Map? _nextState; /// Reference to the value of [context] from the previous render cycle, used internally for proxying /// the ReactJS lifecycle method. @@ -277,7 +288,7 @@ abstract class Component { /// > in ReactJS 16. @Deprecated('This legacy, unstable context API is only supported in the deprecated Component, and not Component2.' ' Instead, use Component2.context, Context.Consumer, or useContext.') - Map prevContext; + Map? prevContext; /// Reference to the value of [state] from the previous render cycle, used internally for proxying /// the ReactJS lifecycle method and [componentDidUpdate]. @@ -285,7 +296,7 @@ abstract class Component { /// Not available after [componentDidUpdate] is called. /// /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. - Map prevState; + Map? prevState; /// Public getter for [_nextState]. /// @@ -298,7 +309,7 @@ abstract class Component { /// [componentWillUpdate] as well as the context-specific variants. /// /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. - Map nextProps; + Map? nextProps; /// Transfers `Component` [_nextState] to [state], and [state] to [prevState]. /// @@ -310,7 +321,7 @@ abstract class Component { void transferComponentState() { prevState = state; if (_nextState != null) { - state = _nextState; + state = _nextState!; } _nextState = Map.from(state); } @@ -318,7 +329,7 @@ abstract class Component { /// Force a call to [render] by calling [setState], which effectively "redraws" the `Component`. /// /// Optionally accepts a [callback] that gets called after the component updates. - void redraw([Function() callback]) { + void redraw([Function()? callback]) { setState({}, callback); } @@ -329,9 +340,9 @@ abstract class Component { /// Also allows [newState] to be used as a transactional `setState` callback. /// /// See: - void setState(covariant dynamic newState, [Function() callback]) { + void setState(covariant dynamic newState, [Function()? callback]) { if (newState is Map) { - _nextState.addAll(newState); + _nextState!.addAll(newState); } else if (newState is StateUpdaterCallback) { _transactionalSetStateCallbacks.add(newState); } else if (newState != null) { @@ -350,7 +361,7 @@ abstract class Component { /// /// See: @Deprecated('Use setState instead.') - void replaceState(Map newState, [Function() callback]) { + void replaceState(Map? newState, [Function()? callback]) { final nextState = newState == null ? {} : Map.from(newState); _nextState = nextState; if (callback != null) _setStateCallbacks.add(callback); @@ -420,7 +431,7 @@ abstract class Component { @Deprecated('This legacy, unstable context API is only supported in the deprecated Component, and not Component2.' ' Instead, use Component2.context, Context.Consumer, or useContext.') // ignore: avoid_returning_null - bool shouldComponentUpdateWithContext(Map nextProps, Map nextState, Map nextContext) => null; + bool? shouldComponentUpdateWithContext(Map nextProps, Map nextState, Map? nextContext) => null; /// ReactJS lifecycle method that is invoked immediately before rendering when [nextProps] or [nextState] are being /// received. @@ -453,7 +464,7 @@ abstract class Component { /// > This will be completely removed alongside the Component class. @Deprecated('This legacy, unstable context API is only supported in the deprecated Component, and not Component2.' ' Instead, use Component2.context, Context.Consumer, or useContext.') - void componentWillUpdateWithContext(Map nextProps, Map nextState, Map nextContext) {} + void componentWillUpdateWithContext(Map nextProps, Map nextState, Map? nextContext) {} /// ReactJS lifecycle method that is invoked immediately after the `Component`'s updates are flushed to the DOM. /// @@ -586,7 +597,7 @@ abstract class Component2 implements Component { /// } /// /// See: - Context get contextType => null; + Context? get contextType => null; /// Invoked once and cached when [registerComponent] is called. Values in the mapping will be set on [props] /// if that prop is not specified by the parent component. @@ -655,10 +666,10 @@ abstract class Component2 implements Component { dynamic context; @override - Map props; + late Map props; // [1] @override - Map state; + late Map state; // [1] @override @Deprecated('Only supported in the deprecated Component, and not in Component2. See doc comment for more info.') @@ -670,7 +681,7 @@ abstract class Component2 implements Component { /// The JavaScript [`ReactComponent`](https://reactjs.org/docs/react-api.html#reactdom.render) /// instance of this `Component` returned by [render]. @override - ReactComponent jsThis; + late ReactComponent jsThis; // [1] /// Allows the [ReactJS `displayName` property](https://reactjs.org/docs/react-component.html#displayname) /// to be set for debugging purposes. @@ -681,8 +692,8 @@ abstract class Component2 implements Component { /// This will result in the dart2js name being `ReactDartComponent2` (the /// name of the proxying JS component defined in _dart_helpers.js). @override - String get displayName { - String value; + String? get displayName { + String? value; assert(() { value = runtimeType.toString(); return true; @@ -690,7 +701,7 @@ abstract class Component2 implements Component { return value; } - Component2Bridge get _bridge => Component2Bridge.forComponent(this); + Component2Bridge get _bridge => Component2Bridge.forComponent(this)!; /// Triggers a rerender with new state obtained by shallow-merging [newState] into the current [state]. /// @@ -700,7 +711,7 @@ abstract class Component2 implements Component { /// /// See: @override - void setState(Map newState, [SetStateCallback callback]) { + void setState(Map? newState, [SetStateCallback? callback]) { _bridge.setState(this, newState, callback); } @@ -710,14 +721,14 @@ abstract class Component2 implements Component { /// Optionally accepts a [callback] that gets called after the component updates. /// /// See: - void setStateWithUpdater(StateUpdaterCallback updater, [SetStateCallback callback]) { + void setStateWithUpdater(StateUpdaterCallback updater, [SetStateCallback? callback]) { _bridge.setStateWithUpdater(this, updater, callback); } /// Causes [render] to be called, skipping [shouldComponentUpdate]. /// /// > See: - void forceUpdate([SetStateCallback callback]) { + void forceUpdate([SetStateCallback? callback]) { _bridge.forceUpdate(this, callback); } @@ -769,7 +780,7 @@ abstract class Component2 implements Component { /// > [Consider recommended alternative solutions first!](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions) /// /// See: - Map getDerivedStateFromProps(Map nextProps, Map prevState) => null; + Map? getDerivedStateFromProps(Map nextProps, Map prevState) => null; /// ReactJS lifecycle method that is invoked before rendering when [nextProps] and/or [nextState] are being received. /// @@ -876,7 +887,7 @@ abstract class Component2 implements Component { /// [getDerivedStateFromError] will be ignored. /// /// See: - Map getDerivedStateFromError(dynamic error) => null; + Map? getDerivedStateFromError(dynamic error) => null; /// Allows usage of PropValidator functions to check the validity of a prop within the props passed to it. /// @@ -919,8 +930,7 @@ abstract class Component2 implements Component { /// ``` /// /// See: - // ignore: prefer_void_to_null - Map> get propTypes => {}; + Map> get propTypes => {}; /// Examines [props] and [state] and returns one of the following types: /// @@ -959,7 +969,7 @@ abstract class Component2 implements Component { /// See: @override @Deprecated('Use forceUpdate or setState({}) instead. Will be removed when Component is removed.') - void redraw([SetStateCallback callback]) { + void redraw([SetStateCallback? callback]) { setState({}, callback); } @@ -1168,7 +1178,7 @@ abstract class Component2 implements Component { /// Will be removed when [Component] is removed in a future major release. @override @Deprecated('Only supported in the deprecated Component, and not in Component2. See doc comment for more info.') - void replaceState(Map newState, [SetStateCallback callback]) => throw _unsupportedError('replaceState'); + void replaceState(Map? newState, [SetStateCallback? callback]) => throw _unsupportedError('replaceState'); /// Do not use. /// @@ -1189,7 +1199,7 @@ abstract class Component2 implements Component { /// Will be removed when [Component] is removed in a future major release. @override @Deprecated('Only supported in the deprecated Component, and not in Component2. See doc comment for more info.') - initComponentInternal(props, _jsRedraw, [RefMethod ref, _jsThis, context]) => + initComponentInternal(props, _jsRedraw, [RefMethod? ref, _jsThis, context]) => throw _unsupportedError('initComponentInternal'); /// Do not use. @@ -1278,35 +1288,35 @@ abstract class Component2 implements Component { @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - Map _context; + Map? _context; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - var _jsRedraw; + late void Function() _jsRedraw; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - Map _nextState; + Map? _nextState; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - Map _props; + late Map _props; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - RefMethod _ref; + late RefMethod _ref; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - List _setStateCallbacks; + late List _setStateCallbacks; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - Map _state; + late Map _state; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') - List _transactionalSetStateCallbacks; + late List _transactionalSetStateCallbacks; @override @Deprecated('Only supported in the deprecated Component, and not in Component2.') @@ -1358,7 +1368,7 @@ mixin TypedSnapshot { } /// Creates a ReactJS virtual DOM instance ([ReactElement] on the client). -abstract class ReactComponentFactoryProxy implements Function { +abstract class ReactComponentFactoryProxy { /// The type of component created by this factory. get type; @@ -1366,12 +1376,12 @@ abstract class ReactComponentFactoryProxy implements Function { /// /// Necessary to work around DDC `dart.dcall` issues in , /// since invoking the function directly doesn't work. - dynamic /*ReactElement*/ build(Map props, [List childrenArgs]); + ReactElement build(Map props, [List childrenArgs]); /// Returns a new rendered component instance with the specified [props] and `children` ([c1], [c2], et. al.). /// /// > The additional children arguments (c2, c3, et. al.) are a workaround for . - dynamic /*ReactElement*/ call(Map props, + ReactElement call(Map props, [c1 = _notSpecified, c2 = _notSpecified, c3 = _notSpecified, @@ -1519,601 +1529,603 @@ ComponentRegistrar2 registerComponent2 = validateJsApiThenReturn(() => registrat FunctionComponentRegistrar registerFunctionComponent = validateJsApiThenReturn(() => registration_utils.registerFunctionComponent); +ReactDomComponentFactoryProxy _createDomFactory(String tagName) { + validateJsApi(); + return ReactDomComponentFactoryProxy(tagName); +} + /// The HTML `` `AnchorElement`. -dynamic a = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('a')); +ReactDomComponentFactoryProxy a = _createDomFactory('a'); /// The HTML `` `Element`. -dynamic abbr = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('abbr')); +ReactDomComponentFactoryProxy abbr = _createDomFactory('abbr'); /// The HTML `
` `Element`. -dynamic address = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('address')); +ReactDomComponentFactoryProxy address = _createDomFactory('address'); /// The HTML `` `AreaElement`. -dynamic area = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('area')); +ReactDomComponentFactoryProxy area = _createDomFactory('area'); /// The HTML `
` `Element`. -dynamic article = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('article')); +ReactDomComponentFactoryProxy article = _createDomFactory('article'); /// The HTML `