Skip to content

Commit

Permalink
Merge pull request #941 from Workiva/FED-3114-lazy
Browse files Browse the repository at this point in the history
FED-3114: Lazy
  • Loading branch information
rmconsole7-wk authored Oct 7, 2024
2 parents 36081e4 + 6040cc7 commit c7db23b
Show file tree
Hide file tree
Showing 15 changed files with 2,122 additions and 223 deletions.
67 changes: 50 additions & 17 deletions example/suspense/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,56 @@

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

<!-- javascript -->
<!-- javascript -->
</head>
<body>
<div id="content"></div>

</head>
<body>
<div id="content"></div>

<script src="packages/react/react_prod.js"></script>
<script src="packages/react/react_dom_prod.js"></script>

<script type="application/javascript" defer src="main.dart.js"></script>
</body>
<script src="packages/react/react_prod.js"></script>
<script src="packages/react/react_dom_prod.js"></script>
<script>
const defaultMessageContext = React.createContext('default context value');
window.TestJsComponent = React.forwardRef(function (props, ref) {
const {
buttonProps = {},
listOfProps = [],
inputRef,
buttonComponent = 'button',
inputComponent = 'input',
component = 'span',
children,
messageContext = defaultMessageContext,
...rest
} = props;
let message = React.useContext(messageContext);
if (typeof message !== 'string') {
// Work around react-dart always wrapping values in an object (FED-467)
// whose value is under a property `Symbol('react-dart.context')`.
// Since it's a local symbol, we can't construct a matching symbol, so we find the matching one.
const symbol = Object.getOwnPropertySymbols(message).find((s) =>
s.description.includes('react-dart.context')
);
message = message[symbol];
}
return React.createElement(
'div',
{},
React.createElement(buttonComponent, buttonProps),
React.createElement('li', listOfProps[0]),
React.createElement(inputComponent, { type: 'text', ref: inputRef }),
React.createElement(component, { ...rest, ref }, children),
React.createElement('div', { role: 'alert' }, message)
);
});
</script>
<script type="application/javascript" defer src="main.dart.js"></script>
</body>
</html>
49 changes: 0 additions & 49 deletions example/suspense/lazy.dart

This file was deleted.

15 changes: 7 additions & 8 deletions example/suspense/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@ import 'dart:html';
import 'package:over_react/over_react.dart';
import 'package:react/react_dom.dart' as react_dom;
import 'counter_component.dart' deferred as lazy_component;
import 'lazy.dart';
import 'third_party_file.dart';

final LazyCounter = lazy(() async {
await lazy_component.loadLibrary();
UiFactory<CounterPropsMixin> LazyCounter = lazy(() async {
await Future.delayed(Duration(seconds: 5));
await lazy_component.loadLibrary();
return lazy_component.Counter;
},
UiFactoryConfig(
propsFactory: PropsFactory.fromUiFactory(CounterPropsMapView),
displayName: 'This does nothing...',
));
UiFactoryConfig(
propsFactory: PropsFactory.fromUiFactory(CounterPropsMapView)
)
);

void main() {
react_dom.render(
Expand All @@ -38,7 +37,7 @@ void main() {
'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!',
)
)(
(LazyCounter()..initialCount = 2)(
(LazyCounter())(
(Dom.div()..id = 'Heyyy!')(),
),
),
Expand Down
1 change: 1 addition & 0 deletions lib/over_react.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export 'src/util/guid_util.dart';
export 'src/util/hoc.dart';
export 'src/util/handler_chain_util.dart';
export 'src/util/key_constants.dart';
export 'src/util/lazy.dart';
export 'src/util/map_util.dart';
export 'src/util/memo.dart';
export 'src/util/pretty_print.dart';
Expand Down
111 changes: 111 additions & 0 deletions lib/src/util/lazy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2020 Workiva Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

library over_react.lazy;

import 'package:over_react/over_react.dart';
import 'package:react/react.dart' as react;

import '../component_declaration/function_component.dart';

/// A HOC that creates a "lazy" component that lets you defer loading a component’s code until it is rendered for the first time.
///
/// Returns a `UiFactory` you can use just render in your tree. While the code for the lazy component is still loading,
/// attempting to render it will suspend. Use <Suspense> to display a loading indicator while it’s loading.
/// [load] is a function that should return a `Future<UiFactory<TProps>>` that resolves to the component to be rendered.
/// [_config] should be a `UiFactoryConfig<TProps>` or `null` and is only `dynamic` to avoid an unnecessary cast in the boilerplate.
///
/// React will not call [load] until the first time the component is rendered.
/// After React first calls [load], it will wait for it to resolve, and then render the resolved value.
/// Both the returned Future and the Future's resolved value will be cached, so React will not call [load] more than once.
/// If the Future rejects, React will throw the rejection reason for the nearest Error Boundary to handle.
///
/// Example:
/// ```dart
/// import 'package:over_react/over_react.dart';
///
/// part 'main.over_react.g.dart';
///
/// mixin ALazyComponentPropsMixin on UiProps {}
///
/// UiFactory<ALazyComponentPropsMixin> ALazyComponent = lazy(
/// () async {
/// final componentModule = await loadComponent();
/// return uiForwardRef(
/// (props, ref) {
/// return (componentModule.AnotherComponent()
/// ..ref = ref
/// ..addProps(props)
/// )(props.children);
/// },
/// _$ALazyComponentConfig,
/// );
/// },
/// _$ALazyComponentConfig
/// );
/// ```
///
/// > __NOTE:__ A lazy component MUST be wrapped with a `Suspense` component to provide a fallback ui while it loads.
///
/// ```dart
/// (Suspense()
/// ..fallback = Dom.p()('Loading...')
/// )(
/// ALazyComponent()(),
/// );
/// ```
/// See: <https://react.dev/reference/react/lazy>.
UiFactory<TProps> lazy<TProps extends UiProps>(
Future<UiFactory<TProps>> Function() load, /* UiFactoryConfig<TProps> */ dynamic _config) {
ArgumentError.checkNotNull(_config, '_config');
if (_config is! UiFactoryConfig<TProps>) {
throw ArgumentError('_config is required when using a custom props class and should be a UiFactoryConfig<TProps>. Make sure you are '
r'using either the generated factory config (i.e. _$FooConfig) or manually declaring your config correctly.');
}
// ignore: invalid_use_of_protected_member
var propsFactory = _config.propsFactory;

final lazyFactoryProxy = react.lazy(() async {
final factory = await load();
return factory().componentFactory!;
});

if (propsFactory == null) {
if (TProps != UiProps && TProps != GenericUiProps) {
throw ArgumentError(
'config.propsFactory must be provided when using custom props classes');
}
propsFactory = PropsFactory.fromUiFactory(
([backingMap]) => GenericUiProps(lazyFactoryProxy, backingMap))
as PropsFactory<TProps>;
}
// Work around propsFactory not getting promoted to non-nullable in _uiFactory: https://github.com/dart-lang/language/issues/1536
final nonNullablePropsFactory = propsFactory;

TProps _uiFactory([Map? props]) {
TProps builder;
if (props == null) {
// propsFactory should get promoted to non-nullable here, but it does not some reason propsF
builder = nonNullablePropsFactory.jsMap(JsBackedMap());
} else if (props is JsBackedMap) {
builder = nonNullablePropsFactory.jsMap(props);
} else {
builder = nonNullablePropsFactory.map(props);
}

return builder..componentFactory = lazyFactoryProxy;
}

return _uiFactory;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
meta: ^1.6.0
package_config: ^2.1.0
path: ^1.5.1
react: ^7.1.0
react: ^7.2.0
redux: ^5.0.0
source_span: ^1.4.1
transformer_utils: ^0.2.6
Expand Down
Loading

0 comments on commit c7db23b

Please sign in to comment.