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

FED-3114: Lazy #941

Merged
merged 33 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8e6fc92
first pass at lazy
kealjones-wk Sep 10, 2024
0ebd41f
uncomment out tests
kealjones-wk Sep 10, 2024
96ba2f8
try different dependency_overrides
kealjones-wk Sep 10, 2024
f5605c2
ugh
kealjones-wk Sep 10, 2024
131ceb9
add lazy tests
kealjones-wk Sep 12, 2024
6b6fd4c
get lazy prop conversion tests going
kealjones-wk Sep 16, 2024
4999c39
Merge branch 'master' into FED-3114-lazy
kealjones-wk Sep 16, 2024
6e112ff
remove unused code and imports
kealjones-wk Sep 16, 2024
41d389e
add a class component map props test
kealjones-wk Sep 16, 2024
3fd3865
undo named extensions
kealjones-wk Sep 16, 2024
d3b00c7
cleanup suspense example
kealjones-wk Sep 16, 2024
677b555
woops
kealjones-wk Sep 16, 2024
1e0b3a7
remove unused part
kealjones-wk Sep 16, 2024
9713881
fix suspense test lazy typing
kealjones-wk Sep 17, 2024
aed0afb
add doc comment
kealjones-wk Sep 17, 2024
e1704f4
fix lazy displyName config
kealjones-wk Sep 17, 2024
046c0f5
add ability to use Generic Props
kealjones-wk Sep 18, 2024
b5289ec
add snippets for lazy uiFunction and lazy uiForwardRef
kealjones-wk Sep 18, 2024
dbf8c32
address more feedback
kealjones-wk Oct 1, 2024
0edb580
apply greg's diff to fix analyzer error
kealjones-wk Oct 2, 2024
0cc1b79
rebuild
kealjones-wk Oct 2, 2024
3edb048
remove wrapper
kealjones-wk Oct 2, 2024
c9a17ea
move UiProps tests into lazy_test so that we can wrap correctly and t…
kealjones-wk Oct 7, 2024
2bb9479
add render success test
kealjones-wk Oct 7, 2024
8d81cf6
bahhh humbug
kealjones-wk Oct 7, 2024
ebf3afe
Fix bad import
kealjones-wk Oct 7, 2024
6c1f3f9
Reorganize tests, add coverage for null case, tweak expectations
greglittlefield-wf Oct 7, 2024
7ba044a
Fix lint
greglittlefield-wf Oct 7, 2024
ddfeb30
Merge pull request #954 from Workiva/FED-3114-lazy--test-organization
kealjones-wk Oct 7, 2024
86c0e50
prevent using null with lazy
kealjones-wk Oct 7, 2024
b3ecc65
add null throws test
kealjones-wk Oct 7, 2024
a956461
format
kealjones-wk Oct 7, 2024
6040cc7
bump react version
kealjones-wk Oct 7, 2024
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
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) {
_config ??= UiFactoryConfig();
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!;
});
greglittlefield-wf marked this conversation as resolved.
Show resolved Hide resolved

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;
}
6 changes: 6 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ workiva:
core_checks:
version: 1
react_boilerplate: disabled

dependency_overrides:
react:
git:
url: https://github.com/Workiva/react-dart.git
ref: FED-3114-lazy
Loading