2.3.0 • Published 3 months ago

@rain-cafe/react-utils v2.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

NPM Version NPM Downloads Coveralls

CI Build Maintainability Semantic Release Code Style: Prettier

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

TypeOutputExample
stringstring'hello world'
numbernumber.toString()'1'
booleanboolean.toString()'true'
undefined'<undefined>''<undefined>'
objectJSON.stringify(object)'{ "hello": "world" }'
arrayJSON.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!" />);

  // ...
});

Want to Contribute?

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