Skip to content

Commit

Permalink
feat: added defer and useLoaderData wrappers with type support
Browse files Browse the repository at this point in the history
- improved documentation
  • Loading branch information
cecilia-sanare committed Feb 24, 2024
1 parent a730ddf commit 26b1193
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 4 deletions.
64 changes: 60 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,23 @@

</div>

# React Utils
# React Utils 🔧

Collection of react utilities curated by the Rainbow Cafe~

## `useCachedState`
- [Hooks](#hooks)
- [`useCachedState`](#usecachedstate)
- [`useSubtleCrypto`](#usesubtlecrypto)
- [React Router](#react-router)
- [`useLoaderData`](#useloaderdata)
- [`defer`](#defer)
- [Testing Utilities](#testing-utilities)
- [`wrap`](#wrap)
- [`wrap.concat`](#wrapconcat)

## Hooks

### `useCachedState`

```tsx
import { useCachedState } from '@rain-cafe/react-utils';
Expand All @@ -30,7 +42,7 @@ export function MySimpleInput({ value: externalValue }: MySimpleInputProps) {
}
```

## `useSubtleCrypto`
### `useSubtleCrypto`

```tsx
import { useSubtleCrypto } from '@rain-cafe/react-utils';
Expand All @@ -46,7 +58,51 @@ export function Profile({ email }: ProfileProps) {
}
```

## `wrap`
## React Router

### `useLoaderData`

```tsx
import { useLoaderData } from '@rain-cafe/react-utils/react-router';

export async function loader() {
return {
hello: 'world',
};
}

export function Profile() {
// No more type casting!
const value = useLoaderData<typeof loader>();

return value.hello;
}
```

### `defer`

```tsx
import { defer, useLoaderData } from '@rain-cafe/react-utils/react-router';

export async function loader() {
// Properly maps the types so our 'useLoaderData' type wrapper can get them!
return defer({
hello: 'world',
hallo: Promise.resolve('welt'),
});
}

export function Profile() {
// No more type casting!
const data = useLoaderData<typeof loader>();

return value.hello;
}
```

## Testing Utilities

### `wrap`

This utility is more for testing purposes to easily create wrappers for other components.

Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions src/react-router.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* c8 ignore start */
export * from './tests/libs/react-router';
export * from './utils/react-router';
/* c8 ignore end */
82 changes: 82 additions & 0 deletions src/utils/__tests__/react-router.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { vi, describe, it, expect } from 'vitest';
import { defer, useLoaderData } from '../react-router';
import { useLoaderData as innerUseLoaderData } from 'react-router-dom';
import { render, screen } from '@testing-library/react';

vi.mock('react-router-dom', async (importOriginal: any) => ({
...(await importOriginal()),
useLoaderData: vi.fn(),
}));
const useLoaderDataMocked = vi.mocked(innerUseLoaderData);

describe('React Router Utils', () => {
describe('fn(defer)', () => {
it('should map the type correctly', () => {
const deferred = defer({
test: ['hello world'],
other_test: 1234,
});

expect(deferred.data.test).toEqual(['hello world']);
expect(deferred.data.other_test).toEqual(1234);
});
});

describe('fn(useLoaderData)', () => {
it('should support basic data', async () => {
const loader = () => ({
hello: 'world',
});

useLoaderDataMocked.mockReturnValue(loader());

const MyComponent = () => {
const data = useLoaderData<typeof loader>();

return data.hello;
};

render(<MyComponent />);

await expect(screen.findByText('world')).resolves.toBeTruthy();
});

it('should support basic promises', async () => {
const loader = async () => ({
hello: await Promise.resolve('world'),
});

useLoaderDataMocked.mockReturnValue(await loader());

const MyComponent = () => {
const data = useLoaderData<typeof loader>();

return data.hello;
};

render(<MyComponent />);

await expect(screen.findByText('world')).resolves.toBeTruthy();
});

it('should support deferring', async () => {
const loader = () => {
return defer({
hello: 'world',
});
};

useLoaderDataMocked.mockReturnValue(loader().data);

const MyComponent = () => {
const data = useLoaderData<typeof loader>();

return data.hello;
};

render(<MyComponent />);

await expect(screen.findByText('world')).resolves.toBeTruthy();
});
});
});
14 changes: 14 additions & 0 deletions src/utils/react-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { LoaderFunction, defer as innerDefer, useLoaderData as innerUseLoaderData } from 'react-router-dom';

export type DeferredData<T extends Record<string, any>> = Omit<ReturnType<typeof innerDefer>, 'data'> & {
data: T;
};

export type DeferFunction = <T extends Record<string, any>>(data: T, init?: number | ResponseInit) => DeferredData<T>;

export type UseLoaderDataFunction = <T extends LoaderFunction>() => Awaited<ReturnType<T>> extends DeferredData<any>
? Awaited<ReturnType<T>>['data']
: Awaited<ReturnType<T>>;

export const defer = innerDefer as DeferFunction;
export const useLoaderData = innerUseLoaderData as UseLoaderDataFunction;

0 comments on commit 26b1193

Please sign in to comment.