neant
Version:
The simplest React state management library - direct mutations, direct destructuring, automatic fine-grained reactivity
336 lines (257 loc) • 7.46 kB
Markdown
# Neant
*[中文文档](README-zh.md)*
The simplest React state management library - direct mutations, direct destructuring, automatic fine-grained reactivity.
The name "Neant" comes from French meaning "nothingness", symbolizing zero mental overhead state management.
## Why Choose Neant?
**Direct mutations, direct destructuring, automatic fine-grained reactivity** - That's all the magic of Neant.
- **Direct data mutation** - No need to return new objects, just mutate directly
- **Direct destructuring** - Destructure states and actions directly from the hook
- **Automatic fine-grained reactivity** - Components only re-render when used states change
- **Derived states with Hooks** - Perfectly aligned with React philosophy, naturally intuitive
- **Zero mental overhead** - No complex concepts to learn, if you know React, you know Neant
- **SSR support** - Provider pattern seamlessly integrates server-side data
## Installation
```bash
npm install neant
```
## Quick Start
### Create Store
```typescript
import { createStore } from 'neant';
const { useAppStore } = createStore((setState) => ({
// States
count: 0,
user: { name: 'John', age: 25 },
// Direct mutation, that simple
increment: () => setState(draft => {
draft.count += 1;
}),
decrement: () => setState(draft => {
draft.count -= 1;
}),
updateUser: (name: string) => setState(draft => {
draft.user.name = name;
}),
// Async is just as simple
fetchUser: async () => {
const response = await fetch('/api/user');
const userData = await response.json();
setState(draft => {
draft.user = userData;
});
},
}));
```
### Use in Components
```tsx
import React from 'react';
function Counter() {
// Direct destructuring, automatic fine-grained subscription
const { count, increment, decrement } = useAppStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
function UserProfile() {
// Only subscribe to needed states, other state changes won't affect this component
const { user, updateUser } = useAppStore();
return (
<div>
<p>User: {user.name} ({user.age})</p>
<button onClick={() => updateUser('Jane')}>
Change Name
</button>
</div>
);
}
```
### Next.js SSR Integration
#### Create Store Context
```typescript
// store/store-context.tsx
import { createContext, useContext, useRef, ReactNode } from 'react';
import { createStore } from 'neant';
interface AppState {
count: number;
user: { name: string; age: number };
}
const createAppStore = (initialData?: Partial<AppState>) => {
const { useAppStore, setState } = createStore<AppState>((setState) => ({
count: 0,
user: { name: 'John', age: 25 },
increment: () => setState(draft => { draft.count += 1; }),
decrement: () => setState(draft => { draft.count -= 1; }),
updateUser: (name: string) => setState(draft => { draft.user.name = name; }),
}));
// Set initial data
if (initialData) {
setState(draft => {
Object.assign(draft, initialData);
});
}
return { useAppStore, setState };
};
const StoreContext = createContext<ReturnType<typeof createAppStore> | null>(null);
export const StoreProvider = ({
children,
initialData
}: {
children: ReactNode;
initialData?: Partial<AppState>
}) => {
const storeRef = useRef<ReturnType<typeof createAppStore> | null>(null);
if (storeRef.current === null) {
storeRef.current = createAppStore(initialData);
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
);
};
export const useAppStore = (): AppState => {
const storeContext = useContext(StoreContext);
if (!storeContext) {
throw new Error('useAppStore must be used within StoreProvider');
}
return storeContext.useAppStore();
};
```
#### App Router
```typescript
// app/page.tsx
import { StoreProvider } from '../store/store-context';
export default async function Page() {
const serverData = await fetchDataOnServer();
return (
<StoreProvider initialData={serverData}>
<YourComponents />
</StoreProvider>
);
}
```
#### Page Router
```typescript
// pages/index.tsx
import { StoreProvider } from '../store/store-context';
export default function Page({ serverData }) {
return (
<StoreProvider initialData={serverData}>
<YourComponents />
</StoreProvider>
);
}
export const getServerSideProps = async () => {
const serverData = await fetchDataOnServer();
return { props: { serverData } };
};
```
## Derived States - Use Hooks, Very React!
```typescript
// Just regular custom hooks, perfectly aligned with React philosophy
const useDouble = () => {
const { count } = useAppStore();
return count * 2;
};
const useUserInfo = () => {
const { user } = useAppStore();
return {
displayName: `${user.name} (${user.age})`,
isAdult: user.age >= 18
};
};
function DerivedStateExample() {
const double = useDouble();
const { displayName, isAdult } = useUserInfo();
return (
<div>
<p>Double count: {double}</p>
<p>User: {displayName}</p>
<p>Is adult: {isAdult ? 'Yes' : 'No'}</p>
</div>
);
}
```
## Complex State Management
```typescript
const { useAppStore } = createStore((setState) => ({
todos: [],
filter: 'all',
loading: false,
// Direct array mutation, Immer handles immutability
addTodo: (text: string) => setState(draft => {
draft.todos.push({
id: Date.now(),
text,
completed: false,
});
}),
toggleTodo: (id: number) => setState(draft => {
const todo = draft.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}),
removeTodo: (id: number) => setState(draft => {
const index = draft.todos.findIndex(t => t.id === id);
if (index !== -1) {
draft.todos.splice(index, 1);
}
}),
// Async operations are also intuitive
fetchTodos: async () => {
setState(draft => { draft.loading = true; });
try {
const response = await fetch('/api/todos');
const todos = await response.json();
setState(draft => {
draft.todos = todos;
draft.loading = false;
});
} catch (error) {
setState(draft => { draft.loading = false; });
}
},
}));
```
## API Reference
### `createStore(stateCreator)`
Create a new store.
**Parameters:**
- `stateCreator`: `(setState, getState) => initialState`
**Returns:**
- `{ useAppStore, setState, getState, subscribe }`
### `setState(updater)`
Update state, supports both sync and async.
**Parameters:**
- `updater`: `(draft) => void | async (draft) => void`
## Example Projects
Check the `examples/` directory:
- **Next.js App Router**: `examples/nextjs-app-router/`
- **Next.js Page Router**: `examples/nextjs-page-router/`
```bash
# App Router example
cd examples/nextjs-app-router
npm install
npm run dev
# Page Router example
cd examples/nextjs-page-router
npm install
npm run dev
```
### Next.js Client Components
Add `'use client';` when using in App Router:
```typescript
'use client';
import { useAppStore } from './store';
```
## License
MIT License
## Contributing
Issues and Pull Requests are welcome!
---
**Direct mutations, direct destructuring, zero mental overhead - That's Neant!**