2.3.0 • Published 3 months ago
@rain-cafe/react-utils v2.3.0
React Utils 🔧
Collection of react utilities curated by the Rainbow Cafe~
Hooks
useCachedState
import { useCachedState } from '@rain-cafe/react-utils';
export type MySimpleInputProps = {
value?: string;
};
export function MySimpleInput({ value: externalValue }: MySimpleInputProps) {
// This is a utility for keeping external properties in-sync with the internal state
const [value, setValue] = useCachedState(() => externalValue, [externalValue]);
return <input value={value} onChange={(event) => setValue(event.target.value)} />;
}
useSubtleCrypto
import { useSubtleCrypto } from '@rain-cafe/react-utils';
export type ProfileProps = {
email?: string;
};
export function Profile({ email }: ProfileProps) {
const hashedEmail = useSubtleCrypto('SHA-256', email);
return <img src={`https://gravatar.com/avatar/${hashedEmail}.jpg`} />;
}
React Router
useLoaderData
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
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;
}
<Await/>
import { defer, useLoaderData, Await } from '@rain-cafe/react-utils/react-router';
export async function loader() {
return defer({
greetings: Promise.resolve(['hello world', 'hallo welt']),
});
}
export function Profile() {
const data = useLoaderData<typeof loader>();
return (
<Await resolve={data.greetings}>
/* Retains the type! */
{(greetings) => (
<>
{greetings.map((greeting, i) => (
<div key={i}>{greeting}</div>
))}
</>
)}
</Await>
);
}
Testing Utilities
hooked
import { useMemo } from 'react';
import { hooked } from '@rain-cafe/react-utils';
const useMyHook = (value: string) => useMemo(() => value, [value]);
const HookedComponent = hooked(useMyHook);
it('should ...', async () => {
// Properties are forwarded to your component as you'd expect
render(<HookedComponent hook="Hello world!" />);
expect(screen.getByText('Hello world!')).toBeTruthy();
});
Multiple Arguments
import { useMemo } from 'react';
import { hooked } from '@rain-cafe/react-utils';
const useMyHook = (value: string, otherValue: string) => useMemo(() => `${value} ${otherValue}`, [value, otherValue]);
const HookedComponent = hooked(useMyHook);
it('should ...', async () => {
// Properties are forwarded to your component as you'd expect
render(<HookedComponent hook={['Hello world!', 'Hallo welt!']} />);
expect(screen.getByText('Hello world! Hallo welt!')).toBeTruthy();
});
Type Map
Type | Output | Example |
---|---|---|
string | string | 'hello world' |
number | number.toString() | '1' |
boolean | boolean.toString() | 'true' |
undefined | '<undefined>' | '<undefined>' |
object | JSON.stringify(object) | '{ "hello": "world" }' |
array | JSON.stringify(array) | '["hello", "world"]' |
wrap
This utility is more for testing purposes to easily create wrappers for other components.
import { wrap } from '@rain-cafe/react-utils';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const Router = wrap(MemoryRouter);
const ReactQuery = wrap(QueryClientProvider, () => ({
client: new QueryClient(),
}));
it('should ...', async () => {
const Component = await Router(ReactQuery(import('../MyComponent.tsx')));
// Properties are forwarded to your component as you'd expect
render(<Component value="Hello world!" />);
// ...
});
wrap.concat
Helper function for wrappers that combines them together, useful if you need the whole kitchen sink!
import { wrap } from '@rain-cafe/react-utils';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const Router = wrap(MemoryRouter);
const ReactQuery = wrap(QueryClientProvider, () => ({
client: new QueryClient(),
}));
const KitchenSink = wrap.concat(Router, ReactQuery);
it('should ...', async () => {
const Component = await KitchenSink(import('../MyComponent.tsx')));
// Properties are forwarded to your component as you'd expect
render(<Component value="Hello world!" />);
// ...
});
Built-Ins
We have a variety of wrappers for libraries built-in to simplify testing!
import { HelmetProvider } from '@rain-cafe/react-utils/react-helmet-async';
import { QueryClientProvider } from '@rain-cafe/react-utils/react-query';
import { MemoryRouter } from '@rain-cafe/react-utils/react-router';
const KitchenSink = wrap.concat(HelmetProvider, QueryClientProvider, MemoryRouter);
it('should ...', async () => {
const Component = await KitchenSink(import('../MyComponent.tsx')));
render(<Component value="Hello world!" />);
// ...
});
2.3.0
3 months ago
2.0.3
3 months ago
2.2.0
3 months ago
2.0.2
3 months ago
2.1.0
3 months ago
2.0.1
3 months ago
2.0.0
3 months ago
1.5.1
3 months ago
1.4.2
3 months ago
1.5.0
3 months ago
1.4.1
3 months ago
1.4.0
3 months ago
1.3.0
4 months ago
1.2.0
4 months ago
1.2.3
4 months ago
1.2.2
4 months ago
1.2.1
4 months ago
1.1.1
4 months ago
1.1.0
4 months ago
1.1.3
4 months ago
1.1.2
4 months ago
1.0.3
10 months ago
1.0.2
10 months ago
1.0.1
10 months ago
1.0.0
10 months ago