// react-stack

High quality, tiny libraries
for your React project.

A set of documented, typed and tested libraries that work great together. Each one does one thing and does it well. Perfect for small or mid-size projects.

8
Packages
<20kb
combined
TS
Typed
MIT
License
// react-test — readable assertions, zero boilerplate
import $ from 'react-test';
import Counter from './Counter';

it('renders with initial value', async () => {
  const counter = $(<Counter />);
  expect(counter.text()).toBe('0');
});

it('increments on click', async () => {
  const counter = $(<Counter />);
  await counter.find('button').click();
  expect(counter.text()).toBe('1');
  await counter.find('button').click();
  expect(counter.text()).toBe('2');
});
react-test

Readable assertions for React. Ship with confidence, fewer bugs.

crossroad
gzip size

Navigation with simple components and hooks. Write cleaner code.

statux
gzip size

Immutable global state with React Hooks. No ceremony required.

form-mate
gzip size

Tiny and elegant form handling. No boilerplate, just clean forms.

react-text
gzip size

Localization for React and React Native. Stays out of your way.

useAsync()
gzip size

Async operations made easy. Loading, errors, data — minimal code.

fch()
gzip size

Easier API calls. Like Axios, but a fraction of the size.

styled-components
gzip size

Real CSS in JavaScript. Visual primitives for the component age.

import $ from 'react-test';
import Form from './Form';

it('submits the form', async () => {
  const onSubmit = jest.fn();
  const form = $(<Form onSubmit={onSubmit} />);

  await form.find('[name="email"]').type('[email protected]');
  await form.find('button').click();

  expect(onSubmit).toHaveBeenCalledWith(
    { email: '[email protected]' }
  );
});
import Router, { Route, Switch } from 'crossroad';

function App() {
  return (
    <Router>
      <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
      <Switch>
        <Route path="/" component={Home} />
        <Route path="/about" component={About} />
      </Switch>
    </Router>
  );
}
import Store, { useStore } from 'statux';

function App() {
  return (
    <Store user={null} cart={[]}>
      <Page />
    </Store>
  );
}

function Header() {
  const [user] = useStore('user');
  return <h1>Hello, {user?.name ?? 'guest'}</h1>;
}
import Form from 'form-mate';

function SignupForm() {
  const onSubmit = ({ name, email }) =>
    register({ name, email });

  return (
    <Form onSubmit={onSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button>Sign up</button>
    </Form>
  );
}
import Text from 'react-text';

const dictionary = {
  hello: { en: 'Hello, World!', es: '¡Hola, Mundo!' },
  bye:   { en: 'Goodbye!',      es: '¡Adiós!'       },
};

function App() {
  return (
    <Text language="es" dictionary={dictionary}>
      <p><Text hello /></p>
      <p><Text bye /></p>
    </Text>
  );
}
import useAsync from 'use-async';

const getUser = async (signal, id) =>
  api.get(`/users/${id}`, { signal });

function UserProfile({ id }) {
  const { data, error, loading } = useAsync(getUser, [id]);

  if (loading) return <Spinner />;
  if (error) return <ErrorPage />;
  return <Profile user={data} />;
}
import fch from 'fch';

// GET — returns parsed JSON directly
const users = await fch('/api/users');

// POST with body
const user = await fch.post('/api/users', { name: 'Alice' });

// Create instance with base URL
const api = fch.create({ baseUrl: 'https://api.example.com' });
const data = await api.get('/users');
import styled from 'styled-components';

const Card = styled.div`
  background: #fff;
  border-radius: 8px;
  padding: 24px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
`;

const Title = styled.h2`
  font-size: 20px;
  color: ${p => p.muted ? '#888' : '#111'};
`;