@inglorious/react-store 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2025 Inglorious Coderz Srl.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,445 @@
1
+ # @inglorious/react-store
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/@inglorious/react-store.svg)](https://www.npmjs.com/package/@inglorious/react-store)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Official React bindings for **[@inglorious/store](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/packages/store)**.
7
+
8
+ Connect your React app to Inglorious Store with a familiar API. Built on `react-redux` for rock-solid performance and compatibility.
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ - **Drop-in Integration**: Works just like `react-redux` with enhanced features for Inglorious Store
15
+ - **Custom `useNotify` Hook**: Dispatch events with a clean, ergonomic API
16
+ - **Flexible Update Modes**:
17
+ - **Eager mode** - Updates process immediately (responsive UIs)
18
+ - **Batched mode** - Updates process on a timer (performance optimization)
19
+ - **Redux DevTools Support**: Full integration with Redux DevTools for debugging
20
+ - **Battle-tested**: Built on `react-redux` for proven performance and stability
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @inglorious/store @inglorious/react-store react react-dom
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Create Your Store
35
+
36
+ ```javascript
37
+ // store.js
38
+ import { createStore } from "@inglorious/store"
39
+
40
+ const types = {
41
+ counter: {
42
+ increment(counter) {
43
+ counter.value++
44
+ },
45
+ decrement(counter) {
46
+ counter.value--
47
+ },
48
+ },
49
+ }
50
+
51
+ const entities = {
52
+ myCounter: { type: "counter", value: 0 },
53
+ }
54
+
55
+ export const store = createStore({ types, entities })
56
+ ```
57
+
58
+ ### 2. Set Up React Bindings
59
+
60
+ ```javascript
61
+ // react-store.js
62
+ import { createReactStore } from "@inglorious/react-store"
63
+ import { store } from "./store"
64
+
65
+ // Eager mode (default) - updates process immediately
66
+ export const { Provider, useSelector, useNotify } = createReactStore(store)
67
+
68
+ // Or batched mode - updates process at 30 FPS
69
+ // export const { Provider, useSelector, useNotify } = createReactStore(store, {
70
+ // mode: "batched",
71
+ // fps: 30
72
+ // })
73
+ ```
74
+
75
+ ### 3. Wrap Your App
76
+
77
+ ```jsx
78
+ // App.jsx
79
+ import { Provider } from "./react-store"
80
+
81
+ function App() {
82
+ return (
83
+ <Provider>
84
+ <Counter />
85
+ </Provider>
86
+ )
87
+ }
88
+ ```
89
+
90
+ ### 4. Use in Components
91
+
92
+ ```jsx
93
+ // Counter.jsx
94
+ import { useNotify, useSelector } from "./react-store"
95
+
96
+ function Counter() {
97
+ const notify = useNotify()
98
+ const value = useSelector((state) => state.myCounter.value)
99
+
100
+ return (
101
+ <div>
102
+ <h1>Count: {value}</h1>
103
+ <button onClick={() => notify("increment")}>+</button>
104
+ <button onClick={() => notify("decrement")}>-</button>
105
+ </div>
106
+ )
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## API Reference
113
+
114
+ ### `createReactStore(store, config?)`
115
+
116
+ Creates React bindings for an Inglorious Store.
117
+
118
+ **Parameters:**
119
+
120
+ - `store` (required): An Inglorious Store instance
121
+ - `config` (optional): Configuration object
122
+ - `mode`: `"eager"` (default) or `"batched"`
123
+ - `fps`: Frame rate for batched mode (default: 30)
124
+ - `skippedEvents`: Array of event types to exclude from DevTools logging
125
+
126
+ **Returns:**
127
+
128
+ - `Provider`: React context provider component (pre-configured with your store)
129
+ - `useSelector`: Hook to select state slices
130
+ - `useNotify`: Hook to dispatch events
131
+
132
+ **Examples:**
133
+
134
+ ```javascript
135
+ // Eager mode (immediate updates)
136
+ const { Provider, useSelector, useNotify } = createReactStore(store)
137
+
138
+ // Batched mode (30 FPS)
139
+ const { Provider, useSelector, useNotify } = createReactStore(store, {
140
+ mode: "batched",
141
+ fps: 30,
142
+ })
143
+
144
+ // Custom FPS for animations
145
+ const { Provider, useSelector, useNotify } = createReactStore(store, {
146
+ mode: "batched",
147
+ fps: 60,
148
+ skippedEvents: ["update", "mousemove"], // Don't log these in DevTools
149
+ })
150
+ ```
151
+
152
+ ### `useNotify()`
153
+
154
+ Hook that returns a function to dispatch events.
155
+
156
+ **Returns:**
157
+
158
+ - `notify(type, payload?)`: Function to dispatch events
159
+
160
+ **Usage:**
161
+
162
+ ```jsx
163
+ function TodoItem({ id }) {
164
+ const notify = useNotify()
165
+
166
+ // Simple event
167
+ const handleToggle = () => notify("toggleTodo", id)
168
+
169
+ // Event with complex payload
170
+ const handleRename = (text) => notify("renameTodo", { id, text })
171
+
172
+ return (
173
+ <div>
174
+ <button onClick={handleToggle}>Toggle</button>
175
+ <input onChange={(e) => handleRename(e.target.value)} />
176
+ </div>
177
+ )
178
+ }
179
+ ```
180
+
181
+ ### `useSelector(selector, equalityFn?)`
182
+
183
+ Hook to select and subscribe to state slices. Works exactly like `react-redux`'s `useSelector`.
184
+
185
+ **Parameters:**
186
+
187
+ - `selector`: Function that receives state and returns a slice
188
+ - `equalityFn` (optional): Custom equality function for optimization
189
+
190
+ **Usage:**
191
+
192
+ ```jsx
193
+ function TodoList() {
194
+ // Select a specific entity
195
+ const task = useSelector((state) => state.task1)
196
+
197
+ // Select derived data
198
+ const completedCount = useSelector(
199
+ (state) => Object.values(state).filter((task) => task.completed).length,
200
+ )
201
+
202
+ // With custom equality
203
+ const tasks = useSelector(
204
+ (state) => state.tasks,
205
+ (prev, next) => prev === next, // Shallow equality
206
+ )
207
+
208
+ return <div>...</div>
209
+ }
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Update Modes
215
+
216
+ ### Eager Mode (Default)
217
+
218
+ Best for most apps. Updates process immediately when you call `notify()`.
219
+
220
+ ```javascript
221
+ const { Provider, useSelector, useNotify } = createReactStore(store)
222
+ ```
223
+
224
+ **When to use:**
225
+
226
+ - ✅ Standard apps (forms, CRUD, dashboards)
227
+ - ✅ You want instant feedback on user actions
228
+ - ✅ You're not processing many events per second
229
+
230
+ ### Batched Mode
231
+
232
+ Best for performance-critical apps. Updates process on a fixed timer (FPS).
233
+
234
+ ```javascript
235
+ const { Provider, useSelector, useNotify } = createReactStore(store, {
236
+ mode: "batched",
237
+ fps: 30, // Process events 30 times per second
238
+ })
239
+ ```
240
+
241
+ **When to use:**
242
+
243
+ - ✅ Games or animations
244
+ - ✅ High-frequency events (mouse tracking, real-time data)
245
+ - ✅ You want to batch multiple events into one React render
246
+ - ✅ Performance optimization is critical
247
+
248
+ **FPS Guidelines:**
249
+
250
+ - **60 FPS** - Smooth animations, games
251
+ - **30 FPS (default)** - Good balance for most real-time apps
252
+ - **15-20 FPS** - Live dashboards, lower CPU usage
253
+ - **5-10 FPS** - Background updates, status polling
254
+
255
+ ---
256
+
257
+ ## Redux DevTools Integration
258
+
259
+ Redux DevTools work automatically! Install the [browser extension](https://github.com/reduxjs/redux-devtools) to inspect:
260
+
261
+ - State snapshots
262
+ - Event history
263
+ - Time-travel debugging
264
+
265
+ **In batched mode**, events are automatically grouped by frame for cleaner DevTools logs.
266
+
267
+ ```javascript
268
+ // Skip noisy events from DevTools
269
+ const { Provider, useSelector, useNotify } = createReactStore(store, {
270
+ mode: "batched",
271
+ fps: 30,
272
+ skippedEvents: ["mousemove", "update"], // Don't log these
273
+ })
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Complete Example: Todo App
279
+
280
+ ```javascript
281
+ // store.js
282
+ import { createStore } from "@inglorious/store"
283
+
284
+ const types = {
285
+ form: {
286
+ inputChange(entity, value) {
287
+ entity.value = value
288
+ },
289
+ formSubmit(entity) {
290
+ entity.value = ""
291
+ },
292
+ },
293
+
294
+ list: {
295
+ formSubmit(entity, value) {
296
+ entity.tasks.push({
297
+ id: Date.now(),
298
+ text: value,
299
+ completed: false,
300
+ })
301
+ },
302
+ toggleClick(entity, id) {
303
+ const task = entity.tasks.find((t) => t.id === id)
304
+ if (task) task.completed = !task.completed
305
+ },
306
+ },
307
+ }
308
+
309
+ const entities = {
310
+ form: { type: "form", value: "" },
311
+ list: { type: "list", tasks: [] },
312
+ }
313
+
314
+ export const store = createStore({ types, entities })
315
+ ```
316
+
317
+ ```javascript
318
+ // react-store.js
319
+ import { createReactStore } from "@inglorious/react-store"
320
+ import { store } from "./store"
321
+
322
+ export const { Provider, useSelector, useNotify } = createReactStore(store)
323
+ ```
324
+
325
+ ```jsx
326
+ // App.jsx
327
+ import { Provider } from "./react-store"
328
+ import TodoApp from "./TodoApp"
329
+
330
+ export default function App() {
331
+ return (
332
+ <Provider>
333
+ <TodoApp />
334
+ </Provider>
335
+ )
336
+ }
337
+ ```
338
+
339
+ ```jsx
340
+ // TodoApp.jsx
341
+ import { useNotify, useSelector } from "./react-store"
342
+
343
+ export default function TodoApp() {
344
+ const notify = useNotify()
345
+
346
+ const formValue = useSelector((state) => state.form.value)
347
+ const tasks = useSelector((state) => state.list.tasks)
348
+
349
+ const handleSubmit = (e) => {
350
+ e.preventDefault()
351
+ notify("formSubmit", formValue)
352
+ }
353
+
354
+ return (
355
+ <div>
356
+ <form onSubmit={handleSubmit}>
357
+ <input
358
+ value={formValue}
359
+ onChange={(e) => notify("inputChange", e.target.value)}
360
+ placeholder="Add a task"
361
+ />
362
+ <button type="submit">Add</button>
363
+ </form>
364
+
365
+ <ul>
366
+ {tasks.map((task) => (
367
+ <li key={task.id}>
368
+ <input
369
+ type="checkbox"
370
+ checked={task.completed}
371
+ onChange={() => notify("toggleClick", task.id)}
372
+ />
373
+ <span
374
+ style={{
375
+ textDecoration: task.completed ? "line-through" : "none",
376
+ }}
377
+ >
378
+ {task.text}
379
+ </span>
380
+ </li>
381
+ ))}
382
+ </ul>
383
+ </div>
384
+ )
385
+ }
386
+ ```
387
+
388
+ ---
389
+
390
+ ## TypeScript Support
391
+
392
+ Full TypeScript support coming soon! For now, you can add type assertions:
393
+
394
+ ```typescript
395
+ import type { RootState } from "./store"
396
+
397
+ const value = useSelector((state: RootState) => state.myCounter.value)
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Comparison to Plain react-redux
403
+
404
+ **What's the same:**
405
+
406
+ - `<Provider>` and `useSelector` work identically
407
+ - Full Redux DevTools support
408
+ - Same performance characteristics
409
+
410
+ **What's different:**
411
+
412
+ - ✅ Custom `useNotify` hook instead of `useDispatch`
413
+ - ✅ Batched mode option for performance
414
+ - ✅ Automatic `store.update()` handling
415
+ - ✅ Cleaner API for event-based state management
416
+
417
+ ---
418
+
419
+ ## FAQ
420
+
421
+ **Q: Can I use this with existing react-redux code?**
422
+ A: Yes! The `Provider` and `useSelector` are compatible. You can gradually migrate to `useNotify`.
423
+
424
+ **Q: Should I use eager or batched mode?**
425
+ A: Start with eager (default). Switch to batched if you notice performance issues or are building a game/real-time app.
426
+
427
+ **Q: Does this work with React Native?**
428
+ A: Yes! It works anywhere `react-redux` works.
429
+
430
+ **Q: Can I use Redux middleware?**
431
+ A: Use Inglorious Store middleware instead. See [@inglorious/store docs](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/packages/store).
432
+
433
+ ---
434
+
435
+ ## License
436
+
437
+ MIT © [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
438
+
439
+ This is free and open-source software. Use it however you want!
440
+
441
+ ---
442
+
443
+ ## Contributing
444
+
445
+ Contributions welcome! Please read our [Contributing Guidelines](../../CONTRIBUTING.md) first.
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@inglorious/react-store",
3
+ "version": "1.0.0",
4
+ "description": "Official React bindings for @inglorious/store. Provides hooks and a Provider to connect your React components to the store.",
5
+ "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/IngloriousCoderz/inglorious-engine.git"
10
+ },
11
+ "homepage": "https://inglorious-engine.vercel.app/",
12
+ "bugs": {
13
+ "url": "https://github.com/IngloriousCoderz/inglorious-engine/issues"
14
+ },
15
+ "keywords": [
16
+ "react",
17
+ "react-hooks",
18
+ "hooks",
19
+ "state",
20
+ "state-management",
21
+ "store",
22
+ "redux",
23
+ "react-redux",
24
+ "inglorious-store"
25
+ ],
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./src/index.jsx"
30
+ }
31
+ },
32
+ "files": [
33
+ "src"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "react-redux": "^9.1.2"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^18.2.0",
43
+ "@inglorious/store": "5.2.0"
44
+ },
45
+ "devDependencies": {
46
+ "prettier": "^3.6.2",
47
+ "vite": "^7.1.3",
48
+ "@inglorious/eslint-config": "1.0.1"
49
+ },
50
+ "engines": {
51
+ "node": ">= 22"
52
+ },
53
+ "scripts": {
54
+ "format": "prettier --write '**/*.{js,jsx}'",
55
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"
56
+ }
57
+ }
package/src/index.jsx ADDED
@@ -0,0 +1,52 @@
1
+ import { sendAction } from "@inglorious/store/client/dev-tools"
2
+ import { Provider as BaseProvider, useDispatch, useSelector } from "react-redux"
3
+
4
+ const DEFAULT_CONFIG = { mode: "eager" }
5
+ const ONE_SECOND = 1000
6
+ const DEFAULT_FPS = 30
7
+
8
+ export function createReactStore(store, config = DEFAULT_CONFIG) {
9
+ if (config.mode === "batched") {
10
+ loop(store, config)
11
+ }
12
+
13
+ return { Provider, useSelector, useNotify }
14
+
15
+ function Provider({ children }) {
16
+ return <BaseProvider store={store}>{children}</BaseProvider>
17
+ }
18
+
19
+ function useNotify() {
20
+ const dispatch = useDispatch()
21
+
22
+ return (type, payload) => {
23
+ const action = { type, payload }
24
+ dispatch(action)
25
+
26
+ if (config.mode === "eager") {
27
+ store.update()
28
+ sendAction(action, store.getState())
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ function loop(store, config) {
35
+ const fps = config.fps ?? DEFAULT_FPS
36
+
37
+ setInterval(() => {
38
+ const processedEvents = store.update()
39
+ const skippedEvents = config.skippedEvents ?? []
40
+
41
+ const eventsToLog = processedEvents.filter(
42
+ ({ type }) => !skippedEvents.includes(type),
43
+ )
44
+ if (eventsToLog.length) {
45
+ const action = {
46
+ type: eventsToLog.map(({ type }) => type).join("|"),
47
+ payload: eventsToLog,
48
+ }
49
+ sendAction(action, store.getState())
50
+ }
51
+ }, ONE_SECOND / fps)
52
+ }