Sync/AsyncGlobal State ManagerInside/Outside React
Yes, another state management library alternative 👀
Sync/Async
Global State Manager
Inside/Outside ReactYes, another state management library alternative 👀
Inspiration
This library was highly-inspired by Zustand (opens in a new tab), TanStack Query (opens in a new tab), and SWR (opens in a new tab).
Its DX will be very similar to Zustand's. [See comparison]
What Will You Get
- A global state manager just like Zustand.
- A data fetching state manager just like TanStack Query & SWR, but tastes like Zustand.
- A smaller bundle size package.
import { create } from 'zustand'; // 3.3 kB (gzipped: 1.5 kB)
import { createStore } from 'floppy-disk'; // 1.4 kB (gzipped: 750 B) 🎉
import { QueryClient, QueryClientProvider, useQuery, useInfiniteQuery, useMutation } from '@tanstack/react-query'; // 31.7 kB kB (gzipped: 9.2 kB)
import { createQuery, createMutation } from 'floppy-disk'; // 9.7 kB (gzipped: 3.3 kB) 🎉
🍽
Have a taste?
› Zustand + ReactQuery: demo-zustand-react-query.vercel.app (opens in a new tab) (Total: 309.21 kB)
› Floppy Disk: demo-floppy-disk.vercel.app (opens in a new tab) (Total: 272.63 kB 🎉)
Store
Render: 0
Count: 3
Shape: square
Render: 0
Count: 3
Render: 0
Shape: square
Store
Render: 0
Only re-render when"count value is a multiple of 5" is changed
Render: 0
🔕
Count: 3
Shape: square
Render: 0
import { createStore } from 'floppy-disk';
type MyStore = {
count: number;
shape: string;
increment: () => void;
changeShape: () => void;
};
export const useMyStore = createStore<MyStore>(({ set, get }) => ({
count: 3,
shape: 'square',
increment: () => set((prev) => ({ count: prev.count + 1 })),
changeShape: () => {
const { shape } = get();
let nextShape;
if (shape === 'square') nextShape = 'circle';
else if (shape === 'circle') nextShape = 'triangle';
else nextShape = 'square';
set({ shape: nextShape });
},
}));
Query & Mutation
import { createQuery } from 'floppy-disk';
type Pokemon = { id: string; name: string };
// Without param
const useLatestPokemonQuery = createQuery<undefined, Pokemon>(
async () => {
const res = await fetch('/api/pokemons/latest');
const resJson = await res.json();
if (res.ok) return resJson;
throw resJson;
}
);
// With param
const usePokemonDetailQuery = createQuery<{ id: number }, Pokemon>(
async ({ id }) => {
const res = await fetch(`/api/pokemons/${id}`);
const resJson = await res.json();
if (res.ok) return resJson;
throw resJson;
}
);
function LatestPokemonPage() {
const { isLoading, data, error } = useLatestPokemonQuery();
if (error) return <div>Something went wrong</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Latest pokemon is {data.name}</div>;
}
function PokemonDetailPage({ id }: { id: number }) {
const { isLoading, data, error } = usePokemonDetailQuery({ id });
if (error) return <div>Something went wrong</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Pokemon: {data.name}</div>;
}
Features
- Create Store
- Get/set store inside/outside component
- Very simple way to customize the reactivity (state update subscription)
- Support middleware
- Set state interception
- Store event (
onSubscribe
,onUnsubscribe
, etc.) - Use store as local state manager
- Create Stores
- Same as store, but controlled with a store key
- Create Query & Mutation
- Backend agnostic (support GraphQL & any async function)
- TypeScript ready
- SSR/SSG support
- Custom reactivity (we choose when to re-render)
- Create query
- Dedupe multiple request
- Auto-fetch on mount or manual (lazy query)
- Enable/disable query
- Serve stale data while revalidating
- Retry on error (customizable)
- Optimistic update
- Invalidate query
- Reset query
- Query with param (query key)
- Paginated/infinite query
- Prefetch query
- Suspense mode
- Fetch from inside/outside component
- Get query state inside/outside component
- Create mutation
- Mutate from inside/outside component
- Get mutation state inside/outside component