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

CPLAT-12194 Enable functional components to use consumed props #633

Merged
merged 24 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from 23 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
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