Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
## 7.0.0-wip

- 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).
- APIs that have been no-ops since react-dart 6.0.0
- 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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much guaranteed to break Component subclasses where lifecycle methods reference those fields, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They'll be initialized for most lifecycle methods, except for "static" ones like defaultProps, getDerivedStateFromProps, and getDerivedStateFromError where you shouldn't ever reference props/state.

Besides those "static" lifecycle methods, they'll be initialized by the time we get to lifecycle methods.

The sequence during component mount is:

  • The Dart component class is instantiated
    • non-late class fields are initialized
    • Default constructor is called
  • props are initialized
  • getInitialState or initialState is called
  • state is initialized
  • All non-static lifecycle methods (componentWillMount, componentDidMount, render, etc.) are called

For example:

class FooComponent extends Component2 {
  FooComponent() {
    // props would have always been null here, but in 7.0.0 accessing it throws
    print(props);
  }
  render() => 'foo';
}
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'];
  render() => 'foo';
}
class FooComponent extends Component2 {
  getDerivedStateFromProps(Map nextProps, Map prevState)  {
    // props would have always been null here, but in 7.0.0 accessing it throws
    print(props);
  }
}

Basically, this shouldn't ever happen unless you're doing something wrong. And if you were doing it wrong, you'd probably get a null exception when trying to read off of them. I think I originally had a note to that effect in here, but scrapped it since I couldn't get wording I was happy with. I'll take a stab at re-adding it.


## [6.2.1](https://github.com/Workiva/react-dart/compare/6.2.0...6.2.1)
- [#366] Fix lints and eliminate most implicit casts

Expand Down
45 changes: 44 additions & 1 deletion lib/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class StateHook<T> {
/// The second item in the pair returned by [React.useState].
void Function(dynamic) _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;
Expand All @@ -33,6 +34,7 @@ class StateHook<T> {
/// initialize [_value] to the return value of [init].
///
/// See: <https://reactjs.org/docs/hooks-reference.html#lazy-initial-state>.
@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;
Expand Down Expand Up @@ -171,6 +173,7 @@ class ReducerHook<TState, TAction, TInit> {
/// The second item in the pair returned by [React.useReducer].
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;
Expand All @@ -181,6 +184,7 @@ class ReducerHook<TState, TAction, TInit> {
/// initialize [_state] to the return value of [init(initialArg)].
///
/// See: <https://reactjs.org/docs/hooks-reference.html#lazy-initialization>.
@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));
Expand Down Expand Up @@ -392,7 +396,46 @@ T useContext<T>(Context<T> context) => ContextHelpers.unjsifyNewContext(React.us
/// ```
///
/// Learn more: <https://reactjs.org/docs/hooks-reference.html#useref>.
Ref<T> useRef<T>([T initialValue]) => Ref.useRefInit(initialValue);
Ref<T> useRef<T>([
// 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,
]) =>
useRefInit(initialValue);

/// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue].
///
/// Changes to the [Ref.current] property do not cause the containing [DartFunctionComponent] to re-render.
///
/// The returned [Ref] object will persist for the full lifetime of the [DartFunctionComponent].
/// Compare to [createRef] which returns a new [Ref] object on each render.
///
/// > __Note:__ there are two [rules for using Hooks](https://reactjs.org/docs/hooks-rules.html):
/// >
/// > * Only call Hooks at the top level.
/// > * Only call Hooks from inside a [DartFunctionComponent].
///
/// __Example__:
///
/// ```dart
/// UseRefTestComponent(Map props) {
/// final countRef = useRefInit(0);
///
/// handleClick([_]) {
/// ref.current = ref.current + 1;
/// window.alert('You clicked ${ref.current} times!');
/// }
///
/// return react.Fragment({}, [
/// react.button({'onClick': handleClick}, ['Click me!']),
/// ]);
/// }
/// ```
///
/// Learn more: <https://reactjs.org/docs/hooks-reference.html#useref>.
Ref<T> useRefInit<T>(T initialValue) => Ref.useRefInit(initialValue);

/// Returns a memoized version of the return value of [createFunction].
///
Expand Down
Loading