@ribbon-studios/react-utils
v4.0.1
Published
Collection of react utilities curated by the Ribbon Studios Team~
Downloads
158
Readme
React Utils 🔧
Collection of react utilities curated by Ribbon Studios Team~
Hooks
useCachedState
import { useCachedState } from '@ribbon-studios/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 '@ribbon-studios/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 '@ribbon-studios/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;
}<Await/>
import { useLoaderData, Await } from '@ribbon-studios/react-utils/react-router';
export async function loader() {
return Promise.resolve({
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 '@ribbon-studios/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 '@ribbon-studios/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 '@ribbon-studios/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 '@ribbon-studios/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 '@ribbon-studios/react-utils/react-helmet';
import { QueryClientProvider } from '@ribbon-studios/react-utils/react-query';
import { MemoryRouter } from '@ribbon-studios/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!" />);
// ...
});