A simple, blazingly fast, and deeply type-safe global state manager for React.
Version 3 is here! Ditch the boilerplate and embrace pure, decoupled state.
Note: This is the documentation for v3. Upgrading from v2? Check out the v3 Migration Guide. For older docs, see README-v2.md.
We built Rhino because we were tired of boilerplate-heavy setups just to share a string or a boolean across two components.
- Featherweight & Zero Dependencies: With a minified + gzipped size of just ~860 bytes, React Rhino practically disappears into your bundle. No external dependencies. Just pure React goodness.
- Blazingly Fast: Powered by an event-driven
useSyncExternalStorearchitecture. Components only re-render if the exact state key they are subscribed to changes. No more unnecessary re-renders. No more "Provider Hell". - Type-Safe by Default: Thanks to the
createRhinoStorefactory, you get 100% perfect type inference and autocompletion out-of-the-box. No manual interface definitions required. - Familiar: The syntax is identical to React's native
useState. If you know React, you already know Rhino.
- A monolithic state machine: Rhino intentionally avoids complex middlewares, time-travel debugging, reducers, or deeply nested derived state selectors.
- When to look elsewhere: If you are building a massive enterprise application that heavily relies on complex state middleware, intricate data transformations, or devtools integrations, you should consider sophisticated options like Zustand, Redux Toolkit, or Jotai.
Rhino is for the indie hacker, the clean-code enthusiast, and the pragmatic developer who wants simple, decoupled global state that just works.
# npm
npm install react-rhino
# yarn
yarn add react-rhino
# pnpm
pnpm add react-rhinoUse the createRhinoStore factory function to generate strongly-typed providers and hooks tailored precisely to your store.
Create a central file for your state (e.g., store.ts). Just define a plain object.
import createRhinoStore from 'react-rhino';
const store = {
darkMode: true,
userName: "John Doe",
count: 0
};
// createRhinoStore automatically infers all keys and types from your store object!
export const {
RhinoProvider,
useRhinoState,
useRhinoValue,
useSetRhinoState
} = createRhinoStore(store);Wrap your application (or a part of it) with the generated RhinoProvider.
import { RhinoProvider } from './store';
import Counter from './Counter';
import Header from './Header';
function App() {
return (
<RhinoProvider>
<Header />
<Counter />
</RhinoProvider>
);
}
export default App;Use your generated hooks anywhere inside the provider. Enjoy the magical TypeScript autocompletion!
import { useRhinoState, useSetRhinoState } from './store';
const Counter = () => {
// Full autocompletion for "count", and perfect type inference for `setCount`!
const [count, setCount] = useRhinoState("count");
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
</div>
);
}
export const Header = () => {
// Only updates the state. Doesn't read it.
// This component will NEVER re-render when "count" changes! โก
const setCount = useSetRhinoState("count");
return <button onClick={() => setCount(0)}>Reset Counter</button>;
}Returns a tuple with the current state value and a setter function, identical to useState.
Returns only the state value. Use this if your component only needs to read the state but performs no updates.
Returns only the setter function. Pro-tip: Use this if your component only updates the state without reading it. It guarantees the component will not re-render when the state changes!
Want to see it in action? Check out the example app in the example directory.
cd example
npm install
npm run devBuilt with โค๏ธ by Aromal Anil
Released under the MIT License.