Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FED-1729 Prepare for react-dart 7.0.0 #846

Merged
merged 7 commits into from
Oct 11, 2023
Merged
2 changes: 1 addition & 1 deletion lib/src/component/dom_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ abstract class Dom {

/// Returns a new builder that renders a `<main>` tag with getters/setters for all DOM-related React props,
/// optionally backed by a specified map.
static DomProps main([Map backingMap]) => DomProps(react.main as ReactComponentFactoryProxy, backingMap);
static DomProps main([Map backingMap]) => DomProps(react.htmlMain as ReactComponentFactoryProxy, backingMap);

/// Returns a new builder that renders a `<backingMap>` tag with getters/setters for all DOM-related React props,
/// optionally backed by a specified map.
Expand Down
44 changes: 43 additions & 1 deletion lib/src/component/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,49 @@ T useContext<T>(Context<T> context) => react_hooks.useContext(context.reactDartC
/// ```
///
/// Learn more: <https://reactjs.org/docs/hooks-reference.html#useref>.
Ref<T> useRef<T>([T initialValue]) => react_hooks.useRef(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,
]) =>
react_hooks.useRef(initialValue);

/// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue].
///
/// Changes to the [Ref.current] property do not cause the containing [uiFunction] to re-render.
///
/// The returned [Ref] object will persist for the full lifetime of the [uiFunction].
/// 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 [uiFunction].
///
/// __Example__:
///
/// ```dart
/// UiFactory<UseRefExampleProps> UseRefExample = uiFunction(
/// (props) {
/// final countRef = useRefInit(0);
///
/// handleClick([_]) {
/// ref.current = ref.current + 1;
/// window.alert('You clicked ${ref.current} times!');
/// }
///
/// return Fragment()(
/// (Dom.button()..onClick = handleClick)('Click me!'),
/// );
/// },
/// _$UseRefExampleConfig, // ignore: undefined_identifier
/// );
/// ```
///
/// Learn more: <https://reactjs.org/docs/hooks-reference.html#useref>.
Ref<T> useRefInit<T>(T initialValue) => react_hooks.useRefInit(initialValue);

/// Returns a memoized version of the return value of [createFunction].
///
Expand Down
51 changes: 51 additions & 0 deletions lib/src/component/react_dart_deprecated_forward_ref.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import 'dart:js_util';

import 'package:js/js.dart';
import 'package:meta/meta.dart';
import 'package:react/react_client/component_factory.dart';
import 'package:react/react_client/js_backed_map.dart';
import 'package:react/react_client/js_interop_helpers.dart';
import 'package:react/react_client/react_interop.dart';
import 'package:react/react_client/zone.dart';

/// A copy of the `forwardRef` function that was removed from react-dart in 7.0.0,
/// but is still needed by the deprecated `forwardRef` in over_react.
///
/// We'll remove this copy once we remove over_react's forwardRef in the next major.
@internal
ReactJsComponentFactoryProxy forwardRef(
Function(Map props, Ref ref) wrapperFunction, {
String displayName = 'Anonymous',
}) {
// ignore: invalid_use_of_visible_for_testing_member
final wrappedComponent = allowInterop((JsMap props, ref) => componentZone.run(() {
final dartProps = JsBackedMap.backedBy(props);
for (final value in dartProps.values) {
if (value is Function) {
// Tag functions that came straight from the JS
// so that we know to pass them through as-is during prop conversion.
isRawJsFunctionFromProps[value] = true;
}
}

final dartRef = Ref.fromJs(ref as JsRef);
return wrapperFunction(dartProps, dartRef);
}));
defineProperty(wrappedComponent, 'displayName', JsPropertyDescriptor(value: displayName));

final hoc = React.forwardRef(wrappedComponent);
// ignore: invalid_use_of_protected_member
setProperty(hoc, 'dartComponentVersion', ReactDartComponentVersion.component2);

return ReactJsComponentFactoryProxy(hoc, alwaysReturnChildrenAsList: true);
}

@JS()
@anonymous
class JsPropertyDescriptor {
external factory JsPropertyDescriptor({dynamic value});
}

@JS('Object.defineProperty')
external void defineProperty(dynamic object, String propertyName, JsPropertyDescriptor descriptor);
4 changes: 3 additions & 1 deletion lib/src/component/ref_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import 'package:react/react_client/react_interop.dart' as react_interop;
import 'package:react/react_client.dart';
import 'package:over_react/component_base.dart';

import './react_dart_deprecated_forward_ref.dart' as react_dart_old_forward_ref;

/// Creates a [Ref] object that can be attached to a [ReactElement] via the ref prop.
///
/// __Example__:
Expand Down Expand Up @@ -224,7 +226,7 @@ UiFactory<TProps> Function(UiFactory<TProps>) forwardRef<TProps extends UiProps>
return wrapperFunction(factory(props), ref);
}

ReactComponentFactoryProxy hoc = react_interop.forwardRef(wrapProps, displayName: displayName);
final hoc = react_dart_old_forward_ref.forwardRef(wrapProps, displayName: displayName);
setComponentTypeMeta(hoc.type, isHoc: true, parentType: factory().componentFactory.type);

TProps forwardedFactory([Map props]) {
Expand Down
54 changes: 18 additions & 36 deletions lib/src/component_declaration/component_base_2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import 'package:react/react.dart' as react;
import 'package:react/react_client.dart';
import 'package:react/react_client/bridge.dart';
import 'package:react/react_client/js_backed_map.dart';
import 'package:react/react_client/react_interop.dart';

import 'builder_helpers.dart';
import 'component_type_checking.dart';
Expand Down Expand Up @@ -708,13 +707,13 @@ class UiComponent2BridgeImpl extends Component2BridgeImpl {
JsMap jsifyPropTypes(
covariant UiComponent2 component, covariant Map<String,
/*PropValidator<UiProps>*/Function> propTypes) {
Error _getErrorFromConsumerValidator(
Error/*?*/ _getErrorFromConsumerValidator(
/*PropValidator<UiProps>*/Function _validator,
JsBackedMap _props,
Map _props,
react.PropValidatorInfo _info,
) {
var convertedProps = component.typedPropsFactoryJs(_props);
return _validator(convertedProps, _info) as Error;
final typedProps = component.typedPropsFactory(_props);
return _validator(typedProps, _info) as Error/*?*/;
}

// Add [PropValidator]s for props annotated as required.
Expand All @@ -723,18 +722,15 @@ class UiComponent2BridgeImpl extends Component2BridgeImpl {
consumedProps.props.forEach((prop) {
if (!prop.isRequired) return;

Error requiredPropValidator(
Error/*?*/ requiredPropValidator(
Map _props,
react.PropValidatorInfo _info,
) {
Error consumerError;
Error/*?*/ consumerError;
// Check if the consumer has specified a propType for this key.
if (propTypes[prop.key] != null) {
consumerError = _getErrorFromConsumerValidator(
propTypes[prop.key],
JsBackedMap.from(_props),
_info,
);
final propType = propTypes[prop.key];
if (propType != null) {
consumerError = _getErrorFromConsumerValidator(propType, _props, _info);
}

if (consumerError != null) return consumerError;
Expand All @@ -753,29 +749,15 @@ class UiComponent2BridgeImpl extends Component2BridgeImpl {
});
});

// Wrap consumer-provided and required validators with ones that convert plain props maps into typed ones.
return JsBackedMap.from(newPropTypes.map((_propKey, _validator) {
// ignore: prefer_function_declarations_over_variables
JsPropValidator handlePropValidator = (
JsMap _props, // ignore: avoid_types_on_closure_parameters
String _propName, // ignore: avoid_types_on_closure_parameters
String _componentName, // ignore: avoid_types_on_closure_parameters
String _location, // ignore: avoid_types_on_closure_parameters
String _propFullName, // ignore: avoid_types_on_closure_parameters
// This is a required argument of PropTypes validators but is hidden from the JS consumer.
String secret, // ignore: avoid_types_on_closure_parameters
) {
final error = _getErrorFromConsumerValidator(
_validator,
JsBackedMap.fromJs(_props),
react.PropValidatorInfo(
propName: _propName, componentName: _componentName, location: _location, propFullName: _propFullName),
);
return error == null ? null : JsError(error.toString());
};

return MapEntry(_propKey, allowInterop(handlePropValidator));
})).jsObject;
return super.jsifyPropTypes(component, newPropTypes.map((propKey, validator) {
// Use a LHS-typed function variable so we can easily ensure that the function is typed a
// specific way (especially since the typing can't be inferred due to the use of `Function`).
// ignore: prefer_function_declarations_over_variables
final react.PropValidator<Map> wrappedValidator = (props, info) {
return _getErrorFromConsumerValidator(validator, props, info);
};
return MapEntry(propKey, wrappedValidator);
}));
}

/// A version of [setStateWithTypedUpdater] whose updater is passed typed views
Expand Down
2 changes: 1 addition & 1 deletion lib/src/over_react_redux/over_react_redux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ class ReactJsReactReduxComponentFactoryProxy extends ReactJsContextComponentFact
}) : super(jsClass, isProvider: isProvider, isConsumer: isConsumer, shouldConvertDomProps: shouldConvertDomProps);

@override
ReactElement build(Map props, [List childrenArgs]) {
ReactElement build(Map props, [List childrenArgs = const []]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🏅

return super.build(_generateReduxJsProps(props), childrenArgs);
}

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies:
logging: ^1.0.0
meta: ^1.6.0
path: ^1.5.1
react: ^6.2.0
react: ^6.3.0
redux: '>=3.0.0 <6.0.0'
source_span: ^1.4.1
transformer_utils: ^0.2.6
Expand Down
12 changes: 0 additions & 12 deletions test/over_react/util/component_debug_name_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,6 @@ void main() {
expect(getDebugNameForDartComponent(component), 'TestComponent2');
});
});

group('returns the .displayName getter for a non-mounted component', () {
test('UiComponent component declared with standard boilerplate', () {
final component = TestNonMountedComponentComponent();
expect(getDebugNameForDartComponent(component), component.displayName);
});

test('UiComponent2 component declared with standard boilerplate', () {
final component = TestNonMountedComponent2Component();
expect(getDebugNameForDartComponent(component), component.displayName);
});
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,8 @@ class ExhaustiveDeps extends DiagnosticContributor {
}

// useRef() return value is stable.
if (init.tryCast<InvocationExpression>()?.function.tryCast<Identifier>()?.name == 'useRef') {
if (const {'useRef', 'useRefInit'}
.contains(init.tryCast<InvocationExpression>()?.function.tryCast<Identifier>()?.name)) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,17 @@ final Map<String, List<Map<String, Object>>> tests = {
}, null);
''',
},
{
'name': 'Ref from useRefInit',
'code': r'''
final MyComponent = uiFunction<TestProps>((_) {
final ref = useRefInit(0);
useEffect(() {
print(ref.current);
}, []);
}, null);
''',
},
{
'name': 'Ref from useRef (namespaced)',
'code': r'''
Expand All @@ -618,6 +629,17 @@ final Map<String, List<Map<String, Object>>> tests = {
}, null);
''',
},
{
'name': 'Ref from useRefInit (namespaced)',
'code': r'''
final MyComponent = uiFunction<TestProps>((_) {
final ref = over_react.useRefInit(0);
useEffect(() {
print(ref.current);
}, []);
}, null);
''',
},
{
'code': r'''
StateHook<T> useFunnyState<T>(T initialState) => useState(initialState);
Expand Down
Loading