Skip to content

Commit c7db23b

Browse files
Merge pull request #941 from Workiva/FED-3114-lazy
FED-3114: Lazy
2 parents 36081e4 + 6040cc7 commit c7db23b

15 files changed

+2122
-223
lines changed

example/suspense/index.html

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,56 @@
1616

1717
<!DOCTYPE html>
1818
<html lang="en">
19-
<head>
20-
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
21-
<meta charset="utf-8">
22-
<title>over_react Suspense example</title>
23-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
24-
<meta name="apple-mobile-web-app-capable" content="yes">
25-
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
19+
<head>
20+
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
21+
<meta charset="utf-8" />
22+
<title>over_react Suspense example</title>
23+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
24+
<meta name="apple-mobile-web-app-capable" content="yes" />
25+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
2626

27-
<!-- javascript -->
27+
<!-- javascript -->
28+
</head>
29+
<body>
30+
<div id="content"></div>
2831

29-
</head>
30-
<body>
31-
<div id="content"></div>
32-
33-
<script src="packages/react/react_prod.js"></script>
34-
<script src="packages/react/react_dom_prod.js"></script>
35-
36-
<script type="application/javascript" defer src="main.dart.js"></script>
37-
</body>
32+
<script src="packages/react/react_prod.js"></script>
33+
<script src="packages/react/react_dom_prod.js"></script>
34+
<script>
35+
const defaultMessageContext = React.createContext('default context value');
36+
window.TestJsComponent = React.forwardRef(function (props, ref) {
37+
const {
38+
buttonProps = {},
39+
listOfProps = [],
40+
inputRef,
41+
buttonComponent = 'button',
42+
inputComponent = 'input',
43+
component = 'span',
44+
children,
45+
messageContext = defaultMessageContext,
46+
...rest
47+
} = props;
48+
let message = React.useContext(messageContext);
49+
if (typeof message !== 'string') {
50+
// Work around react-dart always wrapping values in an object (FED-467)
51+
// whose value is under a property `Symbol('react-dart.context')`.
52+
// Since it's a local symbol, we can't construct a matching symbol, so we find the matching one.
53+
const symbol = Object.getOwnPropertySymbols(message).find((s) =>
54+
s.description.includes('react-dart.context')
55+
);
56+
message = message[symbol];
57+
}
58+
return React.createElement(
59+
'div',
60+
{},
61+
React.createElement(buttonComponent, buttonProps),
62+
React.createElement('li', listOfProps[0]),
63+
React.createElement(inputComponent, { type: 'text', ref: inputRef }),
64+
React.createElement(component, { ...rest, ref }, children),
65+
React.createElement('div', { role: 'alert' }, message)
66+
);
67+
});
68+
</script>
69+
<script type="application/javascript" defer src="main.dart.js"></script>
70+
</body>
3871
</html>

example/suspense/lazy.dart

Lines changed: 0 additions & 49 deletions
This file was deleted.

example/suspense/main.dart

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@ import 'dart:html';
1717
import 'package:over_react/over_react.dart';
1818
import 'package:react/react_dom.dart' as react_dom;
1919
import 'counter_component.dart' deferred as lazy_component;
20-
import 'lazy.dart';
2120
import 'third_party_file.dart';
2221

23-
final LazyCounter = lazy(() async {
24-
await lazy_component.loadLibrary();
22+
UiFactory<CounterPropsMixin> LazyCounter = lazy(() async {
2523
await Future.delayed(Duration(seconds: 5));
24+
await lazy_component.loadLibrary();
2625
return lazy_component.Counter;
2726
},
28-
UiFactoryConfig(
29-
propsFactory: PropsFactory.fromUiFactory(CounterPropsMapView),
30-
displayName: 'This does nothing...',
31-
));
27+
UiFactoryConfig(
28+
propsFactory: PropsFactory.fromUiFactory(CounterPropsMapView)
29+
)
30+
);
3231

3332
void main() {
3433
react_dom.render(
@@ -38,7 +37,7 @@ void main() {
3837
'I am a fallback UI that will show while we load the lazy component! The load time is artificially inflated to last an additional 5 seconds just to prove it\'s working!',
3938
)
4039
)(
41-
(LazyCounter()..initialCount = 2)(
40+
(LazyCounter())(
4241
(Dom.div()..id = 'Heyyy!')(),
4342
),
4443
),

lib/over_react.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export 'src/util/guid_util.dart';
101101
export 'src/util/hoc.dart';
102102
export 'src/util/handler_chain_util.dart';
103103
export 'src/util/key_constants.dart';
104+
export 'src/util/lazy.dart';
104105
export 'src/util/map_util.dart';
105106
export 'src/util/memo.dart';
106107
export 'src/util/pretty_print.dart';

lib/src/util/lazy.dart

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2020 Workiva Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
library over_react.lazy;
16+
17+
import 'package:over_react/over_react.dart';
18+
import 'package:react/react.dart' as react;
19+
20+
import '../component_declaration/function_component.dart';
21+
22+
/// A HOC that creates a "lazy" component that lets you defer loading a component’s code until it is rendered for the first time.
23+
///
24+
/// Returns a `UiFactory` you can use just render in your tree. While the code for the lazy component is still loading,
25+
/// attempting to render it will suspend. Use <Suspense> to display a loading indicator while it’s loading.
26+
/// [load] is a function that should return a `Future<UiFactory<TProps>>` that resolves to the component to be rendered.
27+
/// [_config] should be a `UiFactoryConfig<TProps>` or `null` and is only `dynamic` to avoid an unnecessary cast in the boilerplate.
28+
///
29+
/// React will not call [load] until the first time the component is rendered.
30+
/// After React first calls [load], it will wait for it to resolve, and then render the resolved value.
31+
/// Both the returned Future and the Future's resolved value will be cached, so React will not call [load] more than once.
32+
/// If the Future rejects, React will throw the rejection reason for the nearest Error Boundary to handle.
33+
///
34+
/// Example:
35+
/// ```dart
36+
/// import 'package:over_react/over_react.dart';
37+
///
38+
/// part 'main.over_react.g.dart';
39+
///
40+
/// mixin ALazyComponentPropsMixin on UiProps {}
41+
///
42+
/// UiFactory<ALazyComponentPropsMixin> ALazyComponent = lazy(
43+
/// () async {
44+
/// final componentModule = await loadComponent();
45+
/// return uiForwardRef(
46+
/// (props, ref) {
47+
/// return (componentModule.AnotherComponent()
48+
/// ..ref = ref
49+
/// ..addProps(props)
50+
/// )(props.children);
51+
/// },
52+
/// _$ALazyComponentConfig,
53+
/// );
54+
/// },
55+
/// _$ALazyComponentConfig
56+
/// );
57+
/// ```
58+
///
59+
/// > __NOTE:__ A lazy component MUST be wrapped with a `Suspense` component to provide a fallback ui while it loads.
60+
///
61+
/// ```dart
62+
/// (Suspense()
63+
/// ..fallback = Dom.p()('Loading...')
64+
/// )(
65+
/// ALazyComponent()(),
66+
/// );
67+
/// ```
68+
/// See: <https://react.dev/reference/react/lazy>.
69+
UiFactory<TProps> lazy<TProps extends UiProps>(
70+
Future<UiFactory<TProps>> Function() load, /* UiFactoryConfig<TProps> */ dynamic _config) {
71+
ArgumentError.checkNotNull(_config, '_config');
72+
if (_config is! UiFactoryConfig<TProps>) {
73+
throw ArgumentError('_config is required when using a custom props class and should be a UiFactoryConfig<TProps>. Make sure you are '
74+
r'using either the generated factory config (i.e. _$FooConfig) or manually declaring your config correctly.');
75+
}
76+
// ignore: invalid_use_of_protected_member
77+
var propsFactory = _config.propsFactory;
78+
79+
final lazyFactoryProxy = react.lazy(() async {
80+
final factory = await load();
81+
return factory().componentFactory!;
82+
});
83+
84+
if (propsFactory == null) {
85+
if (TProps != UiProps && TProps != GenericUiProps) {
86+
throw ArgumentError(
87+
'config.propsFactory must be provided when using custom props classes');
88+
}
89+
propsFactory = PropsFactory.fromUiFactory(
90+
([backingMap]) => GenericUiProps(lazyFactoryProxy, backingMap))
91+
as PropsFactory<TProps>;
92+
}
93+
// Work around propsFactory not getting promoted to non-nullable in _uiFactory: https://github.com/dart-lang/language/issues/1536
94+
final nonNullablePropsFactory = propsFactory;
95+
96+
TProps _uiFactory([Map? props]) {
97+
TProps builder;
98+
if (props == null) {
99+
// propsFactory should get promoted to non-nullable here, but it does not some reason propsF
100+
builder = nonNullablePropsFactory.jsMap(JsBackedMap());
101+
} else if (props is JsBackedMap) {
102+
builder = nonNullablePropsFactory.jsMap(props);
103+
} else {
104+
builder = nonNullablePropsFactory.map(props);
105+
}
106+
107+
return builder..componentFactory = lazyFactoryProxy;
108+
}
109+
110+
return _uiFactory;
111+
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dependencies:
1515
meta: ^1.6.0
1616
package_config: ^2.1.0
1717
path: ^1.5.1
18-
react: ^7.1.0
18+
react: ^7.2.0
1919
redux: ^5.0.0
2020
source_span: ^1.4.1
2121
transformer_utils: ^0.2.6

0 commit comments

Comments
 (0)