Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a61ac02
Add proof-of-concept prop validation for Component2
greglittlefield-wf Jun 13, 2019
7573e3d
Add react-dart wiring explanation
greglittlefield-wf Jun 13, 2019
c93a7c4
Merge branch 'master' into component-2-prop-validation/spike
kealjones-wk Jul 2, 2019
7d26b80
inital pass at integrating with react-dart
kealjones-wk Jul 3, 2019
7e878bc
Merge branch '3.1.0-wip' into CPLAT-4887-PropTypes
kealjones-wk Jul 5, 2019
e8784ce
update to match react-dart api and add example
kealjones-wk Jul 5, 2019
5d16acd
Use new typing
greglittlefield-wf Jul 9, 2019
e523d53
update typing and formatting in base component
kealjones-wk Jul 9, 2019
d55946d
fix dep override and update method override
kealjones-wk Jul 9, 2019
7be4348
update typing to match react-dart
kealjones-wk Jul 10, 2019
d7eaeba
Merge branch '3.1.0-wip' into CPLAT-4887-PropTypes
kealjones-wk Jul 10, 2019
83859a9
deprecate validateProps and add requiredPropValidators
kealjones-wk Jul 11, 2019
1ecf5ba
update and unskip prop validation tests for Component2
kealjones-wk Jul 16, 2019
e7d7785
Merge branch 'finalize-component2-api/dev' into CPLAT-4887-propTypes
greglittlefield-wf Jul 17, 2019
daaf33b
Merge branch 'finalize-component2-api/dev' into CPLAT-4887-propTypes
greglittlefield-wf Jul 18, 2019
a64aba1
Merge branch '3.1.0-wip' into CPLAT-4887-PropTypes
greglittlefield-wf Jul 23, 2019
0ad4eec
address some feedback
kealjones-wk Jul 26, 2019
94432c2
Merge branch 'CPLAT-4887-PropTypes' of https://github.com/Workiva/ove…
kealjones-wk Jul 26, 2019
3e9fd63
update propType tests to use gregs suggestions
kealjones-wk Jul 29, 2019
c609b6e
fix merge conflicts
kealjones-wk Jul 29, 2019
c9cca43
uhmm
kealjones-wk Jul 29, 2019
37378a0
fix tests for dart2js and some generated files?
kealjones-wk Jul 29, 2019
ff120a0
address cr feedback
kealjones-wk Aug 2, 2019
80c9679
remove dep
kealjones-wk Aug 2, 2019
81b005f
psych.
kealjones-wk Aug 2, 2019
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
81 changes: 80 additions & 1 deletion lib/src/component_declaration/component_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ library over_react.component_declaration.component_base;

import 'dart:async';
import 'dart:collection';
import 'dart:js';

import 'package:meta/meta.dart';
import 'package:over_react/over_react.dart';
Expand Down Expand Up @@ -87,7 +88,85 @@ class UiComponent2BridgeImpl extends Component2BridgeImpl {

@override
JsMap jsifyPropTypes(covariant UiComponent2 component, Map propTypes) {
// todo implement jsifyPropTypes
Error _getErrorFromConsumerValidator(
dynamic _validator,
JsBackedMap _props,
String _propName,
String _componentName,
String _location,
String _propFullName
) {
var convertedProps = component.typedPropsFactoryJs(_props);
Error error = _validator(convertedProps, _propName, _componentName, _location, _propFullName);
return error;
}

// Add [PropValidator]s for props annotated as required.
var newPropTypes = Map.from(propTypes);
component.consumedProps?.forEach((ConsumedProps consumedProps) {
consumedProps.props.forEach((PropDescriptor prop) {
if (!prop.isRequired) return;

Error requiredPropValidator(
Map _props,
String _propName,
String _componentName,
String _location,
String _propFullName,
) {
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),
_propName,
_componentName,
_location,
_propFullName
);
}

if (consumerError != null) return consumerError;

if (prop.isNullable && _props.containsKey(prop.key)) return null;
if (!prop.isNullable && _props[prop.key] != null) return null;

if (_props[_propName] == null) {
return new PropError.required(_propName, prop.errorMessage);
}

return null;
}

newPropTypes[prop.key] = requiredPropValidator;
});
});

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

return MapEntry(_propKey, allowInterop(handlePropValidator));
})).jsObject;
}

/// A version of [setStateWithTypedUpdater] whose updater is passed typed views
Expand Down
66 changes: 48 additions & 18 deletions lib/src/component_declaration/component_base/component_base_2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ abstract class UiComponent2<TProps extends UiProps> extends react.Component2
@override
@Deprecated('4.0.0')
Map copyUnconsumedProps() {
var consumedPropKeys = consumedProps?.map((ConsumedProps consumedProps) => consumedProps.keys) ?? const [];
var consumedPropKeys = consumedProps
?.map((ConsumedProps consumedProps) => consumedProps.keys) ??
const [];

return copyProps(keySetsToOmit: consumedPropKeys);
}
Expand Down Expand Up @@ -127,7 +129,9 @@ abstract class UiComponent2<TProps extends UiProps> extends react.Component2
@override
@Deprecated('4.0.0')
Map copyUnconsumedDomProps() {
var consumedPropKeys = consumedProps?.map((ConsumedProps consumedProps) => consumedProps.keys) ?? const [];
var consumedPropKeys = consumedProps
?.map((ConsumedProps consumedProps) => consumedProps.keys) ??
const [];

return copyProps(onlyCopyDomProps: true, keySetsToOmit: consumedPropKeys);
}
Expand All @@ -152,17 +156,20 @@ abstract class UiComponent2<TProps extends UiProps> extends react.Component2
forwardUnconsumedProps(this.props, propsToUpdate: props, keySetsToOmit:
consumedPropKeys, onlyCopyDomProps: true);
}

/// Returns a copy of this component's props with React props optionally omitted, and
/// with the specified [keysToOmit] and [keySetsToOmit] omitted.
@override
Map copyProps({bool omitReservedReactProps: true, bool onlyCopyDomProps: false, Iterable keysToOmit, Iterable<Iterable> keySetsToOmit}) {
Map copyProps(
{bool omitReservedReactProps: true,
bool onlyCopyDomProps: false,
Iterable keysToOmit,
Iterable<Iterable> keySetsToOmit}) {
return getPropsToForward(this.props,
omitReactProps: omitReservedReactProps,
onlyCopyDomProps: onlyCopyDomProps,
keysToOmit: keysToOmit,
keySetsToOmit: keySetsToOmit
);
keySetsToOmit: keySetsToOmit);
}

/// Throws a [PropError] if [appliedProps] are invalid.
Expand All @@ -181,24 +188,21 @@ abstract class UiComponent2<TProps extends UiProps> extends react.Component2
/// throw new PropError.value(tProps.items, 'items', 'must have an even number of items, because reasons');
/// }
/// }
/// __Deprecated.__ Use [propTypes] instead. Will be removed in the `4.0.0` release.
@Deprecated('4.0.0')
@mustCallSuper
@override
void validateProps(Map appliedProps) {
validateRequiredProps(appliedProps);
throw UnsupportedError('[validateProps] is not supported in Component2, use [propTypes] instead.');
}

/// Validates that props with the `@requiredProp` annotation are present.
/// __Deprecated.__ Use [propTypes] instead. Will be removed in the `4.0.0` release.
@Deprecated('4.0.0')
@mustCallSuper
@override
void validateRequiredProps(Map appliedProps) {
consumedProps?.forEach((ConsumedProps consumedProps) {
consumedProps.props.forEach((PropDescriptor prop) {
if (!prop.isRequired) return;
if (prop.isNullable && appliedProps.containsKey(prop.key)) return;
if (!prop.isNullable && appliedProps[prop.key] != null) return;

throw new PropError.required(prop.key, prop.errorMessage);
});
});
throw UnsupportedError('[validateRequiredProps] is not supported in Component2, use [propTypes] instead.');
}

/// Returns a new ClassNameBuilder with className and blacklist values added from [CssClassPropsMixin.className] and
Expand Down Expand Up @@ -270,6 +274,32 @@ abstract class UiComponent2<TProps extends UiProps> extends react.Component2
// END Typed props helpers
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------

/// Allows usage of PropValidator functions to check the validity of a prop passed to it.
/// When an invalid value is provided for a prop, a warning will be shown in the JavaScript console.
/// For performance reasons, propTypes is only checked in development mode.
///
/// Override with a custom implementation to easily add validation.
///
/// get propTypes => {
/// getPropKey((props) => props.twoObjects, typedPropsFactory):
/// (props, propName, componentName, location, propFullName) {
/// final length = props.twoObjects?.length;
/// if (length != 2) {
/// return new PropError.value(length, propName, 'must have a length of 2');
/// }
/// return null;
/// },
/// };
///
/// `getPropKey` is a staticlly typed helper to get the string key for a prop.
///
/// __Note:__ An improved version of `getPropKey` will be offered once
/// https://jira.atl.workiva.net/browse/CPLAT-6655 is completed.
///
/// For more info see: https://www.npmjs.com/package/prop-types
@override
Map<String, react.PropValidator<TProps>> get propTypes => {};
}

/// The basis for a _stateful_ over_react component that is compatible with ReactJS 16 ([react.Component2]).
Expand Down Expand Up @@ -300,8 +330,8 @@ abstract class UiComponent2<TProps extends UiProps> extends react.Component2
/// )(props.children);
/// }
/// }
abstract class UiStatefulComponent2<TProps extends UiProps, TState extends UiState>
extends UiComponent2<TProps>
abstract class UiStatefulComponent2<TProps extends UiProps,
TState extends UiState> extends UiComponent2<TProps>
implements UiStatefulComponent<TProps, TState> {
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion lib/src/util/prop_key_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import 'dart:collection';
/// __Example:__
///
/// var valuePropKey = getPropKey((props) => props.value, TextInput);
String getPropKey(void accessProp(Map keySpy), Map factory(Map props)) {
String getPropKey<T extends Map>(void accessProp(T keySpy), T factory(Map props)) {
return _getKey((Map keySpy) {
return accessProp(factory(keySpy));
});
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors:
- Workiva UI Platform Chapter <uip@workiva.com>
- Workiva App Frameworks Team <appframeworks@workiva.com>
environment:
sdk: '>=2.1.0 <3.0.0'
sdk: '>=2.2.2 <3.0.0'

dependencies:
analyzer: '>=0.35.0 <0.37.0'
Expand Down
Loading