Skip to content

Commit

Permalink
Merge pull request #9 from jozefini/concept
Browse files Browse the repository at this point in the history
Update version
  • Loading branch information
jozefini authored Feb 21, 2025
2 parents ba73f03 + f1f3762 commit ca97570
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 338 deletions.
193 changes: 117 additions & 76 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
# FinalStore

Intuitive state management for React applications with first-class TypeScript support. It offers both global stores and collection management with minimal boilerplate.

## Installation
#### Installation

```bash
# npm
Expand All @@ -18,104 +14,149 @@ yarn add finalstore
bun add finalstore
```

## Quick Example
Intuitive state management for React applications with first-class TypeScript support. It offers both global stores and collection management with minimal boilerplate.

### Store Example
#### Setup store

```tsx
import { createStore } from 'finalstore';

const settings = createStore({
type StoreStates = {
theme: 'light' | 'dark';
sidebar: {
isOpen: boolean;
width: number;
};
notifications: {
id: string;
message: string;
type: 'info' | 'success' | 'error';
}[];
settings: {
fontSize: number;
language: string;
autoSave: boolean;
};
};

const store = createStore({
states: {
theme: 'light',
notifications: true
},
sidebar: {
isOpen: true,
width: 240
},
notifications: [],
settings: {
fontSize: 14,
language: 'en',
autoSave: true
}
} as StoreStates,
actions: {
toggleTheme: (state) => {
state.theme = state.theme === 'light' ? 'dark' : 'light';
return state.theme;
},
setSidebarWidth: (state, width: number) => {
state.sidebar.width = Math.max(180, Math.min(width, 400));
},
fetchNotifications: async (state) => {
const response = await fetch('https://example.com/notifications');
const notifications = await response.json();
state.notifications = notifications;
return notifications;
},
addNotification: (
state,
notification: Omit<StoreStates['notifications'][0], 'id'>
) => {
state.notifications.push({
...notification,
id: crypto.randomUUID()
});
},
removeNotification: (state, id: string) => {
state.notifications = state.notifications.filter((n) => n.id !== id);
},
updateSettings: (state, settings: Partial<StoreStates['settings']>) => {
state.settings = { ...state.settings, ...settings };
}
},
selectors: {
isDarkMode: (state) => state.theme === 'dark',
notificationCount: (state) => state.notifications.length,
hasNotificationType: (
state,
type: StoreStates['notifications'][0]['type']
) => state.notifications.some((n) => n.type === type),
errorNotifications: (state) =>
state.notifications.filter((n) => n.type === 'error')
},
config: {
name: 'SiteStore',
devtools: true
}
});
```

function App() {
const theme = settings.use((s) => s.theme);
#### Get method `non-reactive`

return (
<div data-theme={theme}>
<button onClick={() => settings.dispatch('toggleTheme')}>
Toggle Theme
</button>
</div>
);
}
```tsx
// Without selector (entire states)
const states = store.get();

// With selector (specific state)
const theme = store.get((states) => states.theme);
const [theme] = store.get((states) => [states.theme]);
const { theme } = store.get((states) => ({ theme: states.theme }));

// With predefined selector
const isDarkMode = store.getSelector.isDarkMode();
const hasErrorNotification = store.getSelector.hasNotificationType('error');
```

### Collection Example
#### Use method `reactive`

```tsx
import { createCollection } from 'finalstore';
function Component() {
// Without selector (entire states)
const states = store.use();

const todos = createCollection({
states: {
text: '',
completed: false
},
actions: {
toggle: (state) => {
state.completed = !state.completed;
}
}
});
// With selector (specific state)
const theme = store.use((states) => states.theme);
const [theme] = store.use((states) => [states.theme]);
const { theme } = store.use((states) => ({ theme: states.theme }));

function TodoList() {
const todoIds = todos.useKeys();
// With predefined selector
const isDarkMode = store.useSelector.isDarkMode();
const hasErrorNotification = store.useSelector.hasNotificationType('error');

return (
<ul>
{todoIds.map((id) => (
<TodoItem key={id} id={id} />
))}
</ul>
);
}

function TodoItem({ id }: { id: string }) {
const todo = todos.use(id);

if (!todo) return null;

return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => todos.dispatch(id, 'toggle')}
/>
<span>{todo.text}</span>
</li>
);
return ( ... )
}
```

## Features
#### Dispatch actions `reactive` and `non-reactive`

```tsx
// Synchronous reactive actions
store.dispatch.toggleTheme();
store.dispatch.setSidebarWidth(300);
store.dispatch.addNotification({ message: 'Hello World', type: 'info' });

- **Type Safety**: Full TypeScript support with type inference for states and actions
- **Collection Management**: Efficient handling of dynamic collections with individual item subscriptions
- **Zero Config**: No providers needed for global stores, just create and use
- **DevTools Integration**: Built-in Redux DevTools support for debugging
// Asynchronous reactive actions
await store.dispatch.fetchNotifications();

## Core Concepts
// Dispatch reactive actions with return values
const theme = store.dispatch.toggleTheme();
const notifications = await store.dispatch.fetchNotifications();

- **States**: Global state management for app-wide data
- **Collections**: Map-based state management for dynamic data sets
- **Scoped State**: Component-level state isolation
- **Type Safety**: First-class TypeScript support
// Dispatch non-reactive actions
store.silentDispatch.updateSettings({ fontSize: 16 });
```

## Why FinalStore?
#### Reset store

- 🎯 **Simple API**: Intuitive methods that feel natural in React
- 🔒 **Type Safe**: Built with TypeScript for robust development
- 🎮 **DevTools**: Built-in Redux DevTools support
- 🔄 **Reactive**: Automatic updates with granular subscriptions
- 🎨 **Flexible**: Global, collection, and scoped state patterns
- 🚀 **Performant**: Optimized renders with deep equality checks
```tsx
store.reset();
```
8 changes: 8 additions & 0 deletions apps/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# docs

## 0.1.1

### Patch Changes

- Update store
- Updated dependencies
- [email protected]

## 0.1.0

### Minor Changes
Expand Down
6 changes: 4 additions & 2 deletions apps/docs/app/(home)/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export const store = createStore({
setText: (state, text: string) => {
state.text = text;
}
}
},
selectors: {}
});

export const collection = createCollection({
Expand All @@ -43,5 +44,6 @@ export const collection = createCollection({
toggle: (state) => {
state.completed = !state.completed;
}
}
},
selectors: {}
});
58 changes: 47 additions & 11 deletions apps/docs/content/docs/collection/dispatch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: dispatch()
icon: Parentheses
---

The `dispatch()` method triggers actions to update a specific item in your collection. It supports both synchronous and asynchronous actions, and can optionally skip notifications to prevent unnecessary re-renders.
The `dispatch()` method triggers actions to update a specific item in your collection. It supports both synchronous and asynchronous actions, and can return values from the actions.

```tsx
import { createCollection } from 'finalstore';
Expand All @@ -15,29 +15,65 @@ const users = createCollection({
active: true
},
actions: {
// Sync action with no return
updateName: (state, name: string) => {
state.name = name;
},
// Sync action with return
toggleActive: (state) => {
state.active = !state.active;
return state.active; // Returns the new state
},
fetchUserData: (state, userId: string) => {
// Async action with return
fetchUserData: async (state, userId: string) => {
const data = await api.fetchUser(userId);
state.name = data.name;
state.age = data.age;
return data; // Returns the fetched data
}
}
},
selectors: {}
});
```

## Basic Usage

```tsx
// Using key() to access item-specific actions
const userItem = users.key('user-1');

// Sync action with no return
userItem.dispatch.updateName('John Doe');

// Sync action with return value
const isActive = userItem.dispatch.toggleActive(); // Returns boolean
```

// Basic usage
users.dispatch('user-1', 'updateName', 'John Doe');
## Async Actions

// Without payload
users.dispatch('user-1', 'toggleActive');
```tsx
async function loadUser(id: string) {
// Returns the fetched data from the async action
const userData = await users.key(id).dispatch.fetchUserData(id);
console.log('User loaded:', userData);
}
```

## Silent Dispatch

For cases where you want to update state without triggering re-renders:

// With async action
await users.dispatch('user-1', 'fetchUserData', 'user-1');
```tsx
const userItem = users.key('user-1');

// Using silentDispatch
userItem.silentDispatch.updateName('John Smith');

// Skip notification (prevents re-renders)
users.dispatch('user-1', 'updateName', 'John Smith', false);
// Return values work the same
const isActive = userItem.silentDispatch.toggleActive();

// Async actions also work with silentDispatch
const userData = await userItem.silentDispatch.fetchUserData('user-1');
```

The return values work the same way with both `dispatch` and `silentDispatch`.
1 change: 1 addition & 0 deletions apps/docs/content/docs/collection/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"set",
"get",
"use",
"dispatch",
"remove",
"getSize",
"getKeys",
Expand Down
Loading

0 comments on commit ca97570

Please sign in to comment.