Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion docs/pages/en-US/docs/item.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ console.log(

## Async item

Sometimes it is not necessary to get a dependency in a synchronous way. You could use Async Item and `import` function of webpack to lazy load a dependency. To do that, you should provide an object that implements the following interface:
Sometimes it is not necessary to get a dependency in a synchronous way. For example, you could use Async Item and `import` function of webpack to lazy load a module.

To do that, you should provide an object that implements the following interface:

```ts
export interface AsyncDependencyItem<T> {
Expand Down
24 changes: 24 additions & 0 deletions docs/pages/en-US/docs/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,27 @@ class Dashboard extends React.Component {
private readonly platformSrv!: PlatformService;
}
```

## Use Async Dependencies in React 19

You can also use async dependencies in function components. You need `useAsyncDependency` hook to get the async dependency.

```ts
function Dashboard() {
const platformService = useAsyncDependency(PlatformService);
}
```

It is suggested to use async dependencies with `Suspense`.

```tsx
function Wrapper() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Dashboard />
</Suspense>
);
}
```

Since `useAsyncDependency` uses React's built-in `use` API, it can only be used in version 19 and above.
2 changes: 1 addition & 1 deletion src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class IdentifierUndefinedError extends RediError {
index
}th parameter of "${prettyPrintIdentifier(
target,
)}". Please make sure that there is not cyclic dependency among your TypeScript files, or consider using "forwardRef". For more info please visit our website https://redi.wendell.fun/docs/faq#could-not-find-dependency-registered-on`;
)}". Please make sure that there is not cyclic dependency among your TypeScript files, or consider using "forwardRef". For more info please visit our website https://redi.wzhu.dev/docs/faq#could-not-find-dependency-registered-on`;

super(msg);
}
Expand Down
2 changes: 1 addition & 1 deletion src/publicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ if (globalObject[__REDI_GLOBAL_LOCK__]) {
if (!isNode) {
console.error(`[redi]: You are loading scripts of redi more than once! This may cause undesired behavior in your application.
Maybe your dependencies added redi as its dependency and bundled redi to its dist files. Or you import different versions of redi.
For more info please visit our website: https://redi.wendell.fun/en-US/docs/faq#import-scripts-of-redi-more-than-once`);
For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#import-scripts-of-redi-more-than-once`);
}
} else {
globalObject[__REDI_GLOBAL_LOCK__] = true;
Expand Down
30 changes: 29 additions & 1 deletion src/react-bindings/__tests__/react.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
import type { IDisposable } from '@wendellhu/redi';
import { act, fireEvent, render } from '@testing-library/react';
import { createIdentifier, Injector } from '@wendellhu/redi';

import {
connectDependencies,
connectInjector,
RediContext,
useAsyncDependency,
useDependency,
useInjector,
WithDependency,
} from '@wendellhu/redi/react-bindings';

import React from 'react';

import { afterEach, describe, expect, it } from 'vitest';
import { AA, bbI } from '../../__testing__/async/async.base';
import { expectToThrow } from '../../__testing__/expectToThrow';
import { TEST_ONLY_clearKnownIdentifiers } from '../../decorators';

Expand Down Expand Up @@ -177,4 +179,30 @@ describe('react', () => {

expect(disposed).toBe(true);
});

it('should support async dependency in React 19', async () => {
const j = new Injector([
[AA],
[
bbI,
{
useAsync: () =>
import('../../__testing__/async/async.item').then(
(module) => module.BBFactory,
),
},
],
]);

const App = connectInjector(() => {
const bbi = useAsyncDependency(bbI);
expect(bbi).not.toBeInstanceOf(Promise);

return <div>Hello</div>;
}, j);

await act(() => {
render(<App />);
});
});
});
2 changes: 1 addition & 1 deletion src/react-bindings/publicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export { connectDependencies, connectInjector } from './reactComponent';
export { RediConsumer, RediContext, RediProvider } from './reactContext';
export { WithDependency } from './reactDecorators';
export { useDependency, useInjector } from './reactHooks';
export { useAsyncDependency, useDependency, useInjector } from './reactHooks';
export * from './reactRx';

const __REDI_CONTEXT_LOCK__ = 'REDI_CONTEXT_LOCK';
Expand Down
19 changes: 18 additions & 1 deletion src/react-bindings/reactHooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
Quantity,
} from '@wendellhu/redi';
import { RediError } from '@wendellhu/redi';
import { useContext, useMemo } from 'react';
import { use, useContext, useMemo } from 'react';
import { RediContext } from './reactContext';

class HooksNotInRediContextError extends RediError {
Expand Down Expand Up @@ -63,3 +63,20 @@ export function useDependency<T>(
[id, quantityOrLookUp, lookUp],
);
}

/**
* A Suspense-friendly version of useDependency that can handle async dependencies.
* When the dependency is async, this hook will suspend the component until the dependency is resolved.
* This hook uses React 19's `use` syntax to work with Suspense.
*
* Note: This hook currently only supports REQUIRED quantity for async dependencies,
* as the injector's getAsync method only supports required dependencies.
*/
export function useAsyncDependency<T>(id: DependencyIdentifier<T>): T {
const injector = useInjector();
const promiseOrValue = useMemo(
() => injector.getAsync<T>(id),
[injector, id],
);
return use(promiseOrValue);
}