Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@ribbon-studios/react-utils

Package Overview
Dependencies
Maintainers
2
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ribbon-studios/react-utils

Collection of react utilities curated by the Ribbon Studios Team~

latest
npmnpm
Version
4.3.1
Version published
Maintainers
2
Created
Source

NPM Version NPM Downloads Coverage

CI Build Semantic Release Code Style: Prettier

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`} />;
}

useLocalStorage

import { useLocalStorage } from '@ribbon-studios/react-utils';

export function Profile() {
  const [value, setValue] = useLocalStorage('hello');

  return value;
}

useSessionStorage

import { useSessionStorage } from '@ribbon-studios/react-utils';

export function Profile() {
  const [value, setValue] = useSessionStorage('hello');

  return value;
}

createThemeHook

Creates 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>
  );
}

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

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!" />);

  // ...
});

Want to Contribute?

FAQs

Package last updated on 20 Mar 2026

Did you know?

Socket

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.

Install

Related posts