
Security News
Node.js Drops Bug Bounty Rewards After Funding Dries Up
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.
@rain-cafe/react-utils
Advanced tools
[![NPM Version][npm-version-image]][npm-url] [![NPM Downloads][npm-downloads-image]][npm-url] [![Coveralls][coveralls-image]][coveralls-url]
Collection of react utilities curated by the Rainbow Cafe~
useCachedStateimport { 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)} />;
}
useSubtleCryptoimport { 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`} />;
}
useLoaderDataimport { 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;
}
deferimport { 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>
);
}
hookedimport { 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"]' |
wrapThis 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.concatHelper 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!" />);
// ...
});
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!" />);
// ...
});
FAQs
[![NPM Version][npm-version-image]][npm-url] [![NPM Downloads][npm-downloads-image]][npm-url] [![Coveralls][coveralls-image]][coveralls-url]
We found that @rain-cafe/react-utils demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.