Skip to content

Commit da7b412

Browse files
committed
address some feedabck
1 parent 7564c85 commit da7b412

File tree

6 files changed

+114
-67
lines changed

6 files changed

+114
-67
lines changed

example/suspense/suspense.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'dart:html';
66
import 'package:js/js.dart';
77
import 'package:react/hooks.dart';
88
import 'package:react/react.dart' as react;
9-
import 'package:react/react_client/react_interop.dart';
109
import 'package:react/react_dom.dart' as react_dom;
1110
import './simple_component.dart' deferred as simple;
1211

@@ -16,7 +15,7 @@ main() {
1615
react_dom.render(content, querySelector('#content'));
1716
}
1817

19-
final lazyComponent = lazy(() async {
18+
final lazyComponent = react.lazy(() async {
2019
await Future.delayed(Duration(seconds: 5));
2120
await simple.loadLibrary();
2221

lib/react.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import 'package:react/src/react_client/private_utils.dart' show validateJsApi, v
2121
export 'package:react/src/context.dart';
2222
export 'package:react/src/prop_validator.dart';
2323
export 'package:react/src/react_client/event_helpers.dart';
24-
export 'package:react/react_client/react_interop.dart' show forwardRef2, createRef, memo2, lazy;
24+
export 'package:react/react_client/react_interop.dart' show forwardRef2, createRef, memo2;
25+
export 'package:react/src/react_client/lazy.dart' show lazy;
2526
export 'package:react/src/react_client/synthetic_event_wrappers.dart' hide NonNativeDataTransfer;
2627
export 'package:react/src/react_client/synthetic_data_transfer.dart' show SyntheticDataTransfer;
2728

lib/react_client/react_interop.dart

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import 'package:react/react_client/js_backed_map.dart';
1818
import 'package:react/react_client/component_factory.dart' show ReactDartWrappedComponentFactoryProxy;
1919
import 'package:react/src/react_client/dart2_interop_workaround_bindings.dart';
2020
import 'package:react/src/typedefs.dart';
21-
22-
import '../src/js_interop_util.dart';
21+
import 'package:react/src/js_interop_util.dart';
2322

2423
typedef ReactJsComponentFactory = ReactElement Function(dynamic props, dynamic children);
2524

@@ -277,65 +276,6 @@ ReactComponentFactoryProxy memo2(ReactComponentFactoryProxy factory,
277276
return ReactDartWrappedComponentFactoryProxy(hoc);
278277
}
279278

280-
/// Defer loading a component's code until it is rendered for the first time.
281-
///
282-
/// The `lazy` function is used to create lazy components in react-dart. Lazy components are able to run asynchronous code only when they are trying to be rendered for the first time, allowing for deferred loading of the component's code.
283-
///
284-
/// To use the `lazy` function, you need to wrap the lazy component with a `Suspense` component. The `Suspense` component allows you to specify what should be displayed while the lazy component is loading, such as a loading spinner or a placeholder.
285-
///
286-
/// Example usage:
287-
/// ```dart
288-
/// import 'package:react/react.dart' show lazy, Suspense;
289-
/// import './simple_component.dart' deferred as simple;
290-
///
291-
/// final lazyComponent = lazy(() async {
292-
/// await simple.loadLibrary();
293-
/// return simple.SimpleComponent;
294-
/// });
295-
///
296-
/// // Wrap the lazy component with Suspense
297-
/// final app = Suspense(
298-
/// {
299-
/// fallback: 'Loading...',
300-
/// },
301-
/// lazyComponent({}),
302-
/// );
303-
/// ```
304-
///
305-
/// Defer loading a component’s code until it is rendered for the first time.
306-
///
307-
/// Lazy components need to be wrapped with `Suspense` to render.
308-
/// `Suspense` also allows you to specify what should be displayed while the lazy component is loading.
309-
ReactComponentFactoryProxy lazy(Future<ReactComponentFactoryProxy> Function() load) {
310-
final hoc = React.lazy(
311-
allowInterop(
312-
() => futureToPromise(
313-
(() async {
314-
final factory = await load();
315-
// By using a wrapper uiForwardRef it ensures that we have a matching factory proxy type given to react-dart's lazy,
316-
// a `ReactDartWrappedComponentFactoryProxy`. This is necessary to have consistent prop conversions since we don't
317-
// have access to the original factory proxy outside of this async block.
318-
final wrapper = forwardRef2((props, ref) {
319-
final children = props['children'];
320-
return factory.build(
321-
{...props, 'ref': ref},
322-
[
323-
if (children != null && !(children is List && children.isEmpty)) children,
324-
],
325-
);
326-
});
327-
return jsify({'default': wrapper.type});
328-
})(),
329-
),
330-
),
331-
);
332-
333-
// Setting this version and wrapping with ReactDartWrappedComponentFactoryProxy
334-
// is only okay because it matches the version and factory proxy of the wrapperFactory above.
335-
setProperty(hoc, 'dartComponentVersion', ReactDartComponentVersion.component2);
336-
return ReactDartWrappedComponentFactoryProxy(hoc);
337-
}
338-
339279
abstract class ReactDom {
340280
static Element? findDOMNode(ReactNode object) => ReactDOM.findDOMNode(object);
341281
static dynamic render(ReactNode component, Element element) => ReactDOM.render(component, element);

lib/src/react_client/lazy.dart

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
import 'dart:js';
3+
import 'dart:js_util';
4+
5+
import 'package:react/react.dart';
6+
import 'package:react/react_client/component_factory.dart';
7+
import 'package:react/react_client/react_interop.dart';
8+
import 'package:react/src/js_interop_util.dart';
9+
10+
/// Defer loading a component's code until it is rendered for the first time.
11+
///
12+
/// The `lazy` function is used to create lazy components in react-dart. Lazy components are able to run asynchronous code only when they are trying to be rendered for the first time, allowing for deferred loading of the component's code.
13+
///
14+
/// To use the `lazy` function, you need to wrap the lazy component with a `Suspense` component. The `Suspense` component allows you to specify what should be displayed while the lazy component is loading, such as a loading spinner or a placeholder.
15+
///
16+
/// Example usage:
17+
/// ```dart
18+
/// import 'package:react/react.dart' show lazy, Suspense;
19+
/// import './simple_component.dart' deferred as simple;
20+
///
21+
/// final lazyComponent = lazy(() async {
22+
/// await simple.loadLibrary();
23+
/// return simple.SimpleComponent;
24+
/// });
25+
///
26+
/// // Wrap the lazy component with Suspense
27+
/// final app = Suspense(
28+
/// {
29+
/// fallback: 'Loading...',
30+
/// },
31+
/// lazyComponent({}),
32+
/// );
33+
/// ```
34+
///
35+
/// Defer loading a component’s code until it is rendered for the first time.
36+
///
37+
/// Lazy components need to be wrapped with `Suspense` to render.
38+
/// `Suspense` also allows you to specify what should be displayed while the lazy component is loading.
39+
ReactComponentFactoryProxy lazy(Future<ReactComponentFactoryProxy> Function() load) {
40+
final hoc = React.lazy(
41+
allowInterop(
42+
() => futureToPromise(
43+
(() async {
44+
final factory = await load();
45+
// By using a wrapper uiForwardRef it ensures that we have a matching factory proxy type given to react-dart's lazy,
46+
// a `ReactDartWrappedComponentFactoryProxy`. This is necessary to have consistent prop conversions since we don't
47+
// have access to the original factory proxy outside of this async block.
48+
final wrapper = forwardRef2((props, ref) {
49+
final children = props['children'];
50+
return factory.build(
51+
{...props, 'ref': ref},
52+
[
53+
if (children != null && !(children is List && children.isEmpty)) children,
54+
],
55+
);
56+
});
57+
return jsify({'default': wrapper.type});
58+
})(),
59+
),
60+
),
61+
);
62+
63+
// Setting this version and wrapping with ReactDartWrappedComponentFactoryProxy
64+
// is only okay because it matches the version and factory proxy of the wrapperFactory above.
65+
// ignore: invalid_use_of_protected_member
66+
setProperty(hoc, 'dartComponentVersion', ReactDartComponentVersion.component2);
67+
return ReactDartWrappedComponentFactoryProxy(hoc);
68+
}

test/react_lazy_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
@JS()
33
library react.react_lazy_test;
44

5+
import 'dart:async';
56
import 'dart:js_util';
67

78
import 'package:js/js.dart';
89
import 'package:react/hooks.dart';
910
import 'package:react/react.dart' as react;
11+
import 'package:react/react_test_utils.dart' as rtu;
1012
import 'package:react/react_client/component_factory.dart';
1113
import 'package:react/react_client/react_interop.dart';
1214
import 'package:test/test.dart';
@@ -15,6 +17,24 @@ import 'factory/common_factory_tests.dart';
1517

1618
main() {
1719
group('lazy', () {
20+
// Event more lazy behavior is tested in `react_suspense_test.dart`
21+
22+
test('correctly throws errors from within load function to the closest error boundary', () async {
23+
const errorString = 'intentional future error';
24+
final errors = [];
25+
final errorCompleter = Completer();
26+
final ThrowingLazyTest = react.lazy(() async { throw Exception(errorString);});
27+
onError(error, info) {
28+
errors.add([error, info]);
29+
errorCompleter.complete();
30+
}
31+
expect(() => rtu.renderIntoDocument(_ErrorBoundary({'onComponentDidCatch': onError}, react.Suspense({'fallback': 'Loading...'}, ThrowingLazyTest({})))), returnsNormally);
32+
await expectLater(errorCompleter.future, completes);
33+
expect(errors, hasLength(1));
34+
expect(errors.first.first, isA<Exception>().having((e) => e.toString(), 'message', contains(errorString)));
35+
expect(errors.first.last, isA<ReactErrorInfo>());
36+
});
37+
1838
group('Dart component', () {
1939
final LazyTest = react.lazy(() async => react.forwardRef2((props, ref) {
2040
useImperativeHandle(ref, () => TestImperativeHandle());
@@ -77,3 +97,23 @@ class TestImperativeHandle {}
7797

7898
@JS()
7999
external ReactClass get _JsFoo;
100+
101+
final _ErrorBoundary = react.registerComponent2(() => _ErrorBoundaryComponent(), skipMethods: []);
102+
103+
class _ErrorBoundaryComponent extends react.Component2 {
104+
@override
105+
get initialState => {'hasError': false};
106+
107+
@override
108+
getDerivedStateFromError(dynamic error) => {'hasError': true};
109+
110+
@override
111+
componentDidCatch(dynamic error, ReactErrorInfo info) {
112+
props['onComponentDidCatch'](error, info);
113+
}
114+
115+
@override
116+
render() {
117+
return (state['hasError'] as bool) ? null : props['children'];
118+
}
119+
}

test/react_suspense_test.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'dart:html';
66

77
import 'package:js/js.dart';
88
import 'package:react/react.dart' as react;
9-
import 'package:react/react_client/react_interop.dart';
109
import 'package:react/react_dom.dart' as react_dom;
1110
import 'package:test/test.dart';
1211

@@ -15,7 +14,7 @@ import './react_suspense_lazy_component.dart' deferred as simple;
1514
main() {
1615
group('Suspense', () {
1716
test('renders fallback UI first followed by the real component', () async {
18-
final lazyComponent = lazy(() async {
17+
final lazyComponent = react.lazy(() async {
1918
await simple.loadLibrary();
2019
await Future.delayed(Duration(seconds: 1));
2120
return simple.SimpleFunctionComponent;
@@ -48,7 +47,7 @@ main() {
4847
});
4948

5049
test('is instant after the lazy component has been loaded once', () async {
51-
final lazyComponent = lazy(() async {
50+
final lazyComponent = react.lazy(() async {
5251
await simple.loadLibrary();
5352
await Future.delayed(Duration(seconds: 1));
5453
return simple.SimpleFunctionComponent;

0 commit comments

Comments
 (0)