Skip to content

Commit

Permalink
Merge pull request #846 from Workiva/prepare-for-react-dart-v7
Browse files Browse the repository at this point in the history
FED-1729 Prepare for react-dart 7.0.0
  • Loading branch information
rmconsole3-wf authored Oct 11, 2023
2 parents c304153 + 1a6161a commit 8cd3f6c
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 54 deletions.
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 []]) {
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

0 comments on commit 8cd3f6c

Please sign in to comment.