Skip to content

Commit

Permalink
Merge pull request #633 from Workiva/CPLAT-12194-functional-consumed-…
Browse files Browse the repository at this point in the history
…props

CPLAT-12194 Enable functional components to use consumed props
  • Loading branch information
rmconsole7-wk authored Oct 26, 2020
2 parents 2780a98 + cf36f6b commit d094d02
Show file tree
Hide file tree
Showing 103 changed files with 2,230 additions and 203 deletions.
48 changes: 43 additions & 5 deletions doc/new_boilerplate_migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ _Preview of new boilerplate:_
* [Use Mixin-Based Props Declaration that Disallows Subclassing](#use-mixin-based-props-declaration-that-disallows-subclassing)
* [Consume props from all props mixins by default](#consume-props-from-all-props-mixins-by-default)
* [Examples](#examples)
* __[Function Component Boilerplate *(coming soon)*](#function-component-boilerplate-coming-soon)__
* __[Function Component Boilerplate](#function-component-boilerplate)__
* [Constraints](#function-component-constraints)
* [Design](#design)
* [Syntax](#syntax)
* __[Upgrading Existing Code](#upgrading-existing-code)__

## Background
Expand Down Expand Up @@ -720,7 +720,7 @@ props, and boilerplate shouldn't change shape drastically when doing so.
don't allow generic type inference of the `props` arg in the function
closure.
### Design
### Syntax
```dart
import 'package:over_react/over_react.dart';
Expand Down Expand Up @@ -765,6 +765,44 @@ UiFactory<FooProps> Foo = uiFunction(
);
```

#### With Consumed Props

Because functional components have no instance that track consumed props, the syntax for passing unconsumed
props changes within functional components.

`UiProps` exposes a field `staticMeta` that can be used to generate an iterable containing props meta for specific mixins.
This is similar to accessing `propsMeta` within a class based component. Using the iterable returned from `staticMeta`'s
APIs (such as `forMixins`), we can generate unconsumed props and pass them to a child component.

This is done like so:
```dart
mixin FooPropsMixin on UiProps {
String passedProp;
}
mixin BarPropsMixin on UiProps {
String nonPassedProp;
}
class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin;
UiFactory<FooBarProps> FooBar = uiFunction(
(props) {
final consumedProps = props.staticMeta.forMixins({BarPropsMixin});
return (Foo()..addUnconsumedProps(props, consumedProps))();
},
$FooBarConfig, // ignore: undefined_identifier
);
UiFactory<FooPropsMixin> Foo = uiFunction(
(props) {
return 'foo: ${props.passedProp}';
},
$FooConfig, // ignore: undefined_identifier
);
```

#### With UiProps

```dart
Expand All @@ -778,7 +816,7 @@ UiFactory<UiProps> Foo = uiFunction(
);
```

#### With propTypes
#### With propTypes (Coming soon!)

```dart
UiFactory<FooProps> Foo = uiFunction(
Expand All @@ -799,7 +837,7 @@ UiFactory<FooProps> Foo = uiFunction(
`getPropTypes` provides a way to set up prop validation within the
same variable initializer.

#### Local function components using just a props mixin (no top-level Factory necessary)
#### Local function components using just a props mixin - no top-level Factory necessary (Coming soon!)

```dart
import 'package:over_react/over_react.dart';
Expand Down
120 changes: 100 additions & 20 deletions doc/props_mixin_component_composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ In our core `UiProps` documentation, the pattern of [composing multiple props mi

This example builds on that, showing a lightweight example a common use-case for such composition.

We'll show two components
We'll show three components

1. A `Bar` component that has its own props API - and default rendering behavior when rendered standalone.
2. A `FooBar` component that has its own props API, in addition to the `Bar` props API. This allows consumers to set props declared in `BarPropsMixin`, which will be forwarded to the `Bar` component it renders.
1. A `Foo` component that has its own props API - and default rendering behavior when rendered standalone.
1. A `FooBar` component that has its own props API, in addition to the `Foo` props API. This allows consumers to set props declared in `FooPropsMixin`, which will be forwarded to the `Foo` component it renders.
1. A `FooBaz` component, the functional version of `FooBar`.

### Bar Component
### Foo Component
```dart
import 'package:over_react/over_react.dart';
part 'bar.over_react.g.dart';
part 'foo.over_react.g.dart';
UiFactory<BarPropsMixin> Bar = _$Bar; // ignore: undefined_identifier
UiFactory<FooPropsMixin> Foo = _$Foo; // ignore: undefined_identifier
mixin BarPropsMixin on UiProps {
mixin FooPropsMixin on UiProps {
Set<int> qux;
}
class BarComponent extends UiComponent2<BarPropsMixin> {
class FooComponent extends UiComponent2<FooPropsMixin> {
@override
get defaultProps => (newProps()
..qux = {1, 2, 3}
Expand All @@ -31,7 +32,7 @@ class BarComponent extends UiComponent2<BarPropsMixin> {
render() {
return (Dom.div()
..modifyProps(addUnconsumedDomProps)
..className = (forwardingClassNameBuilder()..add('bar'))
..className = (forwardingClassNameBuilder()..add('foo'))
)(
'Qux: ',
props.qux.map((n) => n),
Expand All @@ -44,36 +45,36 @@ class BarComponent extends UiComponent2<BarPropsMixin> {
__Which, when rendered on its own - produces the following HTML:__

```html
<div class="bar">Qux: 123</div>
<div class="foo">Qux: 123</div>
```

### Composing Bar using the FooBar component
### Composing Foo using the FooBar component

To compose the `Bar` component using `FooBar`, we'll expose the prop API for both the `BarPropsMixin` and the `FooPropsMixin` like so:
To compose the `Foo` component using `FooBar`, we'll expose the prop API for both the `FooPropsMixin` and the `BarPropsMixin` like so:
```dart
import 'package:over_react/over_react.dart';
import 'bar.dart';
import 'foo.dart';
part 'foo_bar.over_react.g.dart';
UiFactory<FooBarProps> FooBar = _$FooBar; // ignore: undefined_identifier
mixin FooPropsMixin on UiProps {
mixin BarPropsMixin on UiProps {
bool baz;
Set<String> bizzles;
}
class FooBarProps = UiProps with FooPropsMixin, BarPropsMixin;
class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin;
class FooBarComponent extends UiComponent2<FooBarProps> {
// Only consume the props found within FooPropsMixin, so that any prop values
// found in BarPropsMixin get forwarded to the child Bar component via `addUnconsumedProps`.
// Only consume the props found within BarPropsMixin, so that any prop values
// found in FooPropsMixin get forwarded to the child Foo component via `addUnconsumedProps`.
@override
get consumedProps => propsMeta.forMixins({FooPropsMixin});
get consumedProps => propsMeta.forMixins({BarPropsMixin});
@override
render() {
return (Bar()
return (Foo()
..modifyProps(addUnconsumedProps)
..className = (forwardingClassNameBuilder()..add('foo__bar'))
)(
Expand Down Expand Up @@ -116,7 +117,7 @@ main() {

Produces the following HTML:
```html
<div class="bar foo__bar foo__bar--abc">
<div class="foo foo__bar foo__bar--abc">
Qux: 234
<div class="foo__bar__bizzles">
Bizzles:
Expand All @@ -129,4 +130,83 @@ Produces the following HTML:
</div>
```

### Composing Foo using the FooBaz component

To compose the `Foo` component using `FooBaz`, a functional component, we'll expose the prop API for both the `FooPropsMixin` and the `BazPropsMixin` like so:
```dart
import 'package:over_react/over_react.dart';
import 'foo.dart';
part 'foo_baz.over_react.g.dart';
mixin BarPropsMixin on UiProps {
bool baz;
Set<String> bizzles;
}
class FooBazProps = UiProps with BarPropsMixin, FooPropsMixin;
UiFactory<FooBazProps> FooBaz = uiFunction(
(props) {
// Only consume the props found within BarPropsMixin, so that any prop values
// found in FooPropsMixin get forwarded to the child Foo component via `addUnconsumedProps`.
final consumedProps = props.staticMeta.forMixins({BarPropsMixin});
return (Foo()
..addUnconsumedProps(props, consumedProps)
..className = (forwardingClassNameBuilder()..add('foo__baz'))
)(
(Dom.div()..className = 'foo__baz__bizzles')(
'Bizzles: ',
Dom.ol()(
props.bizzles.map(_renderBizzleItem).toList(),
),
),
);
ReactElement _renderBizzleItem(String bizzle) {
return (Dom.li()..key = bizzle)(bizzle);
}
},
$FooBazConfig, // ignore: undefined_identifier
);
```

Which, when composed / rendered like so:
```dart
import 'dart:html';
import 'package:over_react/react_dom.dart' as react_dom;
import 'package:over_react/over_react.dart';
// An example of where the `FooBaz` component might be exported from
import 'package:my_package_name/foobaz.dart';
@override
main() {
final abcFooBaz = (FooBaz()
..className = 'foo_baz--abc'
..aria.label = 'I am FooBaz!'
..qux = {2, 3, 4}
..bizzles = {'a', 'b', 'c'}
)();
react_dom.render(abcFooBaz, querySelector('#some_element_id'));
}
```

Produces the following HTML:
```html
<div class="foo foo__baz foo__baz--abc">
Qux: 234
<div class="foo__baz__bizzles">
Bizzles:
<ol>
<li>a</li>
<li>b</li>
<li>c</li>
</ol>
</div>
</div>
```

> To learn more about the `consumedProps` behavior shown above, check out the [mixin-based prop forwarding documentation](new_boilerplate_migration.md#updated-default-behavior-in-the-mixin-based-syntax).
10 changes: 10 additions & 0 deletions example/builder/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import './src/basic_library.dart';
import './src/generic_inheritance_sub.dart';
import './src/generic_inheritance_super.dart';
import './src/function_component.dart' as function;
import 'src/functional_consumed_props.dart';
import 'src/new_class_consumed_props.dart';

main() {
react_dom.render(
Expand Down Expand Up @@ -59,6 +61,14 @@ main() {
' - ',
componentConstructorsByName[name]().toString(),
)).toList(),
(SomeParent()
..aParentProp = 'parent'
..aPropToBePassed = 'passed'
)(),
(SomeClassParent()
..aParentProp = 'classParent'
..aPropToBePassed = 'passed'
)()
), querySelector('#content')
);
}
Expand Down
8 changes: 8 additions & 0 deletions example/builder/src/abstract_inheritance.over_react.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions example/builder/src/basic.over_react.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions example/builder/src/basic_library.over_react.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions example/builder/src/basic_with_state.over_react.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions example/builder/src/basic_with_type_params.over_react.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d094d02

Please sign in to comment.