
Research
/Security News
Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.
@ribbon-studios/react-utils
Advanced tools
Collection of react utilities curated by the Ribbon Studios Team~
Collection of react utilities curated by Ribbon Studios Team~
useCachedStateimport { 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)} />;
}
useSubtleCryptoimport { 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`} />;
}
useLocalStorageimport { useLocalStorage } from '@ribbon-studios/react-utils';
export function Profile() {
const [value, setValue] = useLocalStorage('hello');
return value;
}
useSessionStorageimport { useSessionStorage } from '@ribbon-studios/react-utils';
export function Profile() {
const [value, setValue] = useSessionStorage('hello');
return value;
}
createThemeHookCreates a hook that automatically updates the data-theme attribute on the body to the active theme.
// use-theme.ts
import { createThemeHook } from '@ribbon-studios/react-utils';
export const useTheme = createThemeHook({
// Ensure you use `as const` or the types won't be correct!
themes: ['light', 'dark'] as const,
// This is the theme that will be used when light mode is preferred
light: 'light',
// This is the theme that will be used when dark mode is preferred
dark: 'dark',
});
type Modes = typeof useTheme.$modes;
// Navigation.tsx
const THEME_LABELS: Record<Modes, string> = {
auto: 'Auto',
light: 'Light',
dark: 'Dark',
};
const NEXT_THEME: Record<Modes, Modes> = {
auto: 'light',
light: 'dark',
dark: 'auto',
};
export function Navigation() {
const [mode, setMode] = useTheme();
const nextTheme = useCallback(() => {
setMode((mode) => NEXT_THEME[mode]);
}, [setMode]);
return (
<div>
<button onClick={nextTheme}>{THEME_LABELS[mode]}</button>
</div>
);
}
useLoaderDataimport { 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>
);
}
wrapThis 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.concatHelper 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!" />);
// ...
});
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!" />);
// ...
});
FAQs
Collection of react utilities curated by the Ribbon Studios Team~
We found that @ribbon-studios/react-utils demonstrated a healthy version release cadence and project activity because the last version was released less than 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.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.

Research
/Security News
The North Korean malware loader hides in a Packagist-listed package and its GitHub branch to fetch and execute remote code in a likely Contagious Interview-style lure.

Security News
The Rust project is moving toward formal rules on LLM use in contributions after months of internal debate over maintainer burden, code quality, and contributor experience.