@rplx/react 0.2.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/README.md +74 -0
- package/dist/index.d.mts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +196 -0
- package/dist/index.mjs +154 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @rplx/react
|
|
2
|
+
|
|
3
|
+
React bindings for Ripple state management library.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rplx/core @rplx/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { createStore } from '@rplx/core'
|
|
15
|
+
import { StoreProvider, useStoreState, useDispatch } from '@rplx/react'
|
|
16
|
+
|
|
17
|
+
// Create your store
|
|
18
|
+
const store = createStore({
|
|
19
|
+
initialState: { count: 0 }
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Wrap your app with StoreProvider
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<StoreProvider store={store}>
|
|
26
|
+
<Counter />
|
|
27
|
+
</StoreProvider>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Use hooks in components
|
|
32
|
+
function Counter() {
|
|
33
|
+
const state = useStoreState()
|
|
34
|
+
const dispatch = useDispatch()
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div>
|
|
38
|
+
<p>Count: {state.count}</p>
|
|
39
|
+
<button onClick={() => dispatch('increment')}>
|
|
40
|
+
Increment
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### `StoreProvider`
|
|
50
|
+
|
|
51
|
+
Provider component that makes the store available to all child components.
|
|
52
|
+
|
|
53
|
+
**Props:**
|
|
54
|
+
- `store: StoreAPI<State>` - The store instance (created with `createStore()`)
|
|
55
|
+
- `children: ReactNode` - Child components
|
|
56
|
+
|
|
57
|
+
### `useStoreState<State>()`
|
|
58
|
+
|
|
59
|
+
Hook that subscribes to store state changes and returns the current state. Component will re-render when state changes.
|
|
60
|
+
|
|
61
|
+
**Returns:** `State` - The current store state
|
|
62
|
+
|
|
63
|
+
### `useDispatch<Payload>()`
|
|
64
|
+
|
|
65
|
+
Hook that returns a memoized dispatch function for dispatching events.
|
|
66
|
+
|
|
67
|
+
**Returns:** `(eventKey: string, payload?: Payload) => Promise<void>`
|
|
68
|
+
|
|
69
|
+
### `useStore<State>()`
|
|
70
|
+
|
|
71
|
+
Hook that returns the store instance directly.
|
|
72
|
+
|
|
73
|
+
**Returns:** `StoreAPI<State>` - The store instance (created with `createStore()`)
|
|
74
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { StoreAPI } from '@rplx/core';
|
|
3
|
+
|
|
4
|
+
interface StoreProviderProps<State> {
|
|
5
|
+
store: StoreAPI<State>;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
declare function StoreProvider<State>({ store, children }: StoreProviderProps<State>): React.JSX.Element;
|
|
9
|
+
declare function useStore<State>(): StoreAPI<State>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook to subscribe to store state changes and trigger re-renders
|
|
13
|
+
* Returns the current state and re-renders when state changes
|
|
14
|
+
*/
|
|
15
|
+
declare function useStoreState<State>(): State;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hook to get a dispatch function for the store
|
|
19
|
+
* Returns a memoized dispatch function
|
|
20
|
+
*/
|
|
21
|
+
declare function useDispatch<Payload = any>(): <P = Payload>(eventKey: string, payload?: P | undefined) => Promise<void>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook that leverages shared Subscription objects for optimal React memoization.
|
|
25
|
+
*
|
|
26
|
+
* The key insight: Subscription objects are shared (same instance for same key+params),
|
|
27
|
+
* so we can use the Subscription object itself as a stable reference in dependency arrays.
|
|
28
|
+
* This eliminates the need to memoize params arrays or use JSON.stringify.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* function TodoItem({ id }: { id: string }) {
|
|
33
|
+
* const todo = useSubscription('todoById', id)
|
|
34
|
+
* return <div>{todo.title}</div>
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* function TodoList() {
|
|
38
|
+
* const todos = useSubscription('todos')
|
|
39
|
+
* return <div>{todos.map(...)}</div>
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function useSubscription<Result, Params extends any[] = []>(subscriptionKey: string, ...params: Params): Result;
|
|
44
|
+
/**
|
|
45
|
+
* Factory function that creates a reusable hook for a specific subscription key.
|
|
46
|
+
*
|
|
47
|
+
* This is useful when you want to create a typed hook for a specific subscription,
|
|
48
|
+
* making it easier to use in components without passing the key each time.
|
|
49
|
+
* The store is automatically obtained from context, so no store parameter is needed.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* // Create the hook once (outside component) - no store needed!
|
|
54
|
+
* const useTodos = createSubscriptionHook('todos')
|
|
55
|
+
* const useTodoById = createSubscriptionHook('todoById')
|
|
56
|
+
*
|
|
57
|
+
* // Use in components
|
|
58
|
+
* function TodoList() {
|
|
59
|
+
* const todos = useTodos() // No params needed
|
|
60
|
+
* return <div>{todos.map(...)}</div>
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* function TodoItem({ id }: { id: string }) {
|
|
64
|
+
* const todo = useTodoById(id) // Just params
|
|
65
|
+
* return <div>{todo.title}</div>
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare function createSubscriptionHook<Result, Params extends any[] = []>(subscriptionKey: string): (...params: Params) => Result;
|
|
70
|
+
|
|
71
|
+
export { StoreProvider, createSubscriptionHook, useDispatch, useStore, useStoreState, useSubscription };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { StoreAPI } from '@rplx/core';
|
|
3
|
+
|
|
4
|
+
interface StoreProviderProps<State> {
|
|
5
|
+
store: StoreAPI<State>;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
declare function StoreProvider<State>({ store, children }: StoreProviderProps<State>): React.JSX.Element;
|
|
9
|
+
declare function useStore<State>(): StoreAPI<State>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook to subscribe to store state changes and trigger re-renders
|
|
13
|
+
* Returns the current state and re-renders when state changes
|
|
14
|
+
*/
|
|
15
|
+
declare function useStoreState<State>(): State;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hook to get a dispatch function for the store
|
|
19
|
+
* Returns a memoized dispatch function
|
|
20
|
+
*/
|
|
21
|
+
declare function useDispatch<Payload = any>(): <P = Payload>(eventKey: string, payload?: P | undefined) => Promise<void>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook that leverages shared Subscription objects for optimal React memoization.
|
|
25
|
+
*
|
|
26
|
+
* The key insight: Subscription objects are shared (same instance for same key+params),
|
|
27
|
+
* so we can use the Subscription object itself as a stable reference in dependency arrays.
|
|
28
|
+
* This eliminates the need to memoize params arrays or use JSON.stringify.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* function TodoItem({ id }: { id: string }) {
|
|
33
|
+
* const todo = useSubscription('todoById', id)
|
|
34
|
+
* return <div>{todo.title}</div>
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* function TodoList() {
|
|
38
|
+
* const todos = useSubscription('todos')
|
|
39
|
+
* return <div>{todos.map(...)}</div>
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function useSubscription<Result, Params extends any[] = []>(subscriptionKey: string, ...params: Params): Result;
|
|
44
|
+
/**
|
|
45
|
+
* Factory function that creates a reusable hook for a specific subscription key.
|
|
46
|
+
*
|
|
47
|
+
* This is useful when you want to create a typed hook for a specific subscription,
|
|
48
|
+
* making it easier to use in components without passing the key each time.
|
|
49
|
+
* The store is automatically obtained from context, so no store parameter is needed.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* // Create the hook once (outside component) - no store needed!
|
|
54
|
+
* const useTodos = createSubscriptionHook('todos')
|
|
55
|
+
* const useTodoById = createSubscriptionHook('todoById')
|
|
56
|
+
*
|
|
57
|
+
* // Use in components
|
|
58
|
+
* function TodoList() {
|
|
59
|
+
* const todos = useTodos() // No params needed
|
|
60
|
+
* return <div>{todos.map(...)}</div>
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* function TodoItem({ id }: { id: string }) {
|
|
64
|
+
* const todo = useTodoById(id) // Just params
|
|
65
|
+
* return <div>{todo.title}</div>
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare function createSubscriptionHook<Result, Params extends any[] = []>(subscriptionKey: string): (...params: Params) => Result;
|
|
70
|
+
|
|
71
|
+
export { StoreProvider, createSubscriptionHook, useDispatch, useStore, useStoreState, useSubscription };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
StoreProvider: () => StoreProvider,
|
|
34
|
+
createSubscriptionHook: () => createSubscriptionHook,
|
|
35
|
+
useDispatch: () => useDispatch,
|
|
36
|
+
useStore: () => useStore,
|
|
37
|
+
useStoreState: () => useStoreState,
|
|
38
|
+
useSubscription: () => useSubscription
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/StoreContext.tsx
|
|
43
|
+
var import_react = __toESM(require("react"));
|
|
44
|
+
var StoreContext = (0, import_react.createContext)(null);
|
|
45
|
+
function StoreProvider({ store, children }) {
|
|
46
|
+
const subscribersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
47
|
+
const storeRef = (0, import_react.useRef)(store);
|
|
48
|
+
(0, import_react.useEffect)(() => {
|
|
49
|
+
storeRef.current = store;
|
|
50
|
+
const subscriptionKey = "__ripple_react_state_tracker__";
|
|
51
|
+
try {
|
|
52
|
+
store.registerSubscription(subscriptionKey, {
|
|
53
|
+
compute: (state) => state
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
}
|
|
57
|
+
const unsubscribe = store.subscribe(
|
|
58
|
+
subscriptionKey,
|
|
59
|
+
[],
|
|
60
|
+
(newState) => {
|
|
61
|
+
subscribersRef.current.forEach((callback) => {
|
|
62
|
+
try {
|
|
63
|
+
callback(newState);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Error in store subscriber:", error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
return () => {
|
|
71
|
+
unsubscribe();
|
|
72
|
+
};
|
|
73
|
+
}, [store]);
|
|
74
|
+
const subscribe = import_react.default.useCallback((callback) => {
|
|
75
|
+
subscribersRef.current.add(callback);
|
|
76
|
+
return () => {
|
|
77
|
+
subscribersRef.current.delete(callback);
|
|
78
|
+
};
|
|
79
|
+
}, []);
|
|
80
|
+
return /* @__PURE__ */ import_react.default.createElement(StoreContext.Provider, { value: { store, subscribe } }, children);
|
|
81
|
+
}
|
|
82
|
+
function useStore() {
|
|
83
|
+
const context = (0, import_react.useContext)(StoreContext);
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error("useStore must be used within a StoreProvider");
|
|
86
|
+
}
|
|
87
|
+
return context.store;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/useStoreState.ts
|
|
91
|
+
var import_react2 = require("react");
|
|
92
|
+
function useStoreState() {
|
|
93
|
+
const context = (0, import_react2.useContext)(StoreContext);
|
|
94
|
+
if (!context) {
|
|
95
|
+
throw new Error("useStoreState must be used within a StoreProvider");
|
|
96
|
+
}
|
|
97
|
+
const { store, subscribe } = context;
|
|
98
|
+
const [state, setState] = (0, import_react2.useState)(() => store.getState());
|
|
99
|
+
(0, import_react2.useEffect)(() => {
|
|
100
|
+
const unsubscribe = subscribe((newState) => {
|
|
101
|
+
setState((prevState) => {
|
|
102
|
+
if (prevState !== newState) {
|
|
103
|
+
return newState;
|
|
104
|
+
}
|
|
105
|
+
return prevState;
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
const currentState = store.getState();
|
|
109
|
+
setState(currentState);
|
|
110
|
+
return unsubscribe;
|
|
111
|
+
}, [store, subscribe]);
|
|
112
|
+
return state;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/useDispatch.ts
|
|
116
|
+
var import_react3 = require("react");
|
|
117
|
+
function useDispatch() {
|
|
118
|
+
const store = useStore();
|
|
119
|
+
const dispatch = (0, import_react3.useCallback)(
|
|
120
|
+
(eventKey, payload) => {
|
|
121
|
+
return store.dispatch(eventKey, payload);
|
|
122
|
+
},
|
|
123
|
+
[store]
|
|
124
|
+
);
|
|
125
|
+
return dispatch;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/useSubscription.ts
|
|
129
|
+
var import_react4 = require("react");
|
|
130
|
+
function useSubscription(subscriptionKey, ...params) {
|
|
131
|
+
const store = useStore();
|
|
132
|
+
const subscription = (0, import_react4.useMemo)(
|
|
133
|
+
() => store.getSubscription(subscriptionKey, params),
|
|
134
|
+
[store, subscriptionKey, ...params]
|
|
135
|
+
// Spread params - React will handle equality
|
|
136
|
+
);
|
|
137
|
+
const getSnapshot = (0, import_react4.useCallback)(
|
|
138
|
+
() => store.query(subscription.key, subscription.params),
|
|
139
|
+
[store, subscription]
|
|
140
|
+
// Subscription object is stable, so this is stable too
|
|
141
|
+
);
|
|
142
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
143
|
+
(onStoreChange) => {
|
|
144
|
+
return store.subscribe(
|
|
145
|
+
subscription.key,
|
|
146
|
+
subscription.params,
|
|
147
|
+
() => {
|
|
148
|
+
onStoreChange();
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
[store, subscription]
|
|
153
|
+
// Subscription object is stable, so this is stable too
|
|
154
|
+
);
|
|
155
|
+
return (0, import_react4.useSyncExternalStore)(
|
|
156
|
+
subscribe,
|
|
157
|
+
getSnapshot,
|
|
158
|
+
getSnapshot
|
|
159
|
+
// Server snapshot (same as client for now)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function createSubscriptionHook(subscriptionKey) {
|
|
163
|
+
return function useSubscriptionHook(...params) {
|
|
164
|
+
const store = useStore();
|
|
165
|
+
const subscription = (0, import_react4.useMemo)(
|
|
166
|
+
() => store.getSubscription(subscriptionKey, params),
|
|
167
|
+
[store, subscriptionKey, ...params]
|
|
168
|
+
);
|
|
169
|
+
const getSnapshot = (0, import_react4.useCallback)(
|
|
170
|
+
() => store.query(subscription.key, subscription.params),
|
|
171
|
+
[store, subscription]
|
|
172
|
+
);
|
|
173
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
174
|
+
(onStoreChange) => {
|
|
175
|
+
return store.subscribe(
|
|
176
|
+
subscription.key,
|
|
177
|
+
subscription.params,
|
|
178
|
+
() => {
|
|
179
|
+
onStoreChange();
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
[store, subscription]
|
|
184
|
+
);
|
|
185
|
+
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
189
|
+
0 && (module.exports = {
|
|
190
|
+
StoreProvider,
|
|
191
|
+
createSubscriptionHook,
|
|
192
|
+
useDispatch,
|
|
193
|
+
useStore,
|
|
194
|
+
useStoreState,
|
|
195
|
+
useSubscription
|
|
196
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/StoreContext.tsx
|
|
2
|
+
import React, { createContext, useContext, useRef, useEffect } from "react";
|
|
3
|
+
var StoreContext = createContext(null);
|
|
4
|
+
function StoreProvider({ store, children }) {
|
|
5
|
+
const subscribersRef = useRef(/* @__PURE__ */ new Set());
|
|
6
|
+
const storeRef = useRef(store);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
storeRef.current = store;
|
|
9
|
+
const subscriptionKey = "__ripple_react_state_tracker__";
|
|
10
|
+
try {
|
|
11
|
+
store.registerSubscription(subscriptionKey, {
|
|
12
|
+
compute: (state) => state
|
|
13
|
+
});
|
|
14
|
+
} catch (error) {
|
|
15
|
+
}
|
|
16
|
+
const unsubscribe = store.subscribe(
|
|
17
|
+
subscriptionKey,
|
|
18
|
+
[],
|
|
19
|
+
(newState) => {
|
|
20
|
+
subscribersRef.current.forEach((callback) => {
|
|
21
|
+
try {
|
|
22
|
+
callback(newState);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error("Error in store subscriber:", error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
return () => {
|
|
30
|
+
unsubscribe();
|
|
31
|
+
};
|
|
32
|
+
}, [store]);
|
|
33
|
+
const subscribe = React.useCallback((callback) => {
|
|
34
|
+
subscribersRef.current.add(callback);
|
|
35
|
+
return () => {
|
|
36
|
+
subscribersRef.current.delete(callback);
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
return /* @__PURE__ */ React.createElement(StoreContext.Provider, { value: { store, subscribe } }, children);
|
|
40
|
+
}
|
|
41
|
+
function useStore() {
|
|
42
|
+
const context = useContext(StoreContext);
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error("useStore must be used within a StoreProvider");
|
|
45
|
+
}
|
|
46
|
+
return context.store;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/useStoreState.ts
|
|
50
|
+
import { useState, useEffect as useEffect2, useContext as useContext2 } from "react";
|
|
51
|
+
function useStoreState() {
|
|
52
|
+
const context = useContext2(StoreContext);
|
|
53
|
+
if (!context) {
|
|
54
|
+
throw new Error("useStoreState must be used within a StoreProvider");
|
|
55
|
+
}
|
|
56
|
+
const { store, subscribe } = context;
|
|
57
|
+
const [state, setState] = useState(() => store.getState());
|
|
58
|
+
useEffect2(() => {
|
|
59
|
+
const unsubscribe = subscribe((newState) => {
|
|
60
|
+
setState((prevState) => {
|
|
61
|
+
if (prevState !== newState) {
|
|
62
|
+
return newState;
|
|
63
|
+
}
|
|
64
|
+
return prevState;
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
const currentState = store.getState();
|
|
68
|
+
setState(currentState);
|
|
69
|
+
return unsubscribe;
|
|
70
|
+
}, [store, subscribe]);
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/useDispatch.ts
|
|
75
|
+
import { useCallback } from "react";
|
|
76
|
+
function useDispatch() {
|
|
77
|
+
const store = useStore();
|
|
78
|
+
const dispatch = useCallback(
|
|
79
|
+
(eventKey, payload) => {
|
|
80
|
+
return store.dispatch(eventKey, payload);
|
|
81
|
+
},
|
|
82
|
+
[store]
|
|
83
|
+
);
|
|
84
|
+
return dispatch;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/useSubscription.ts
|
|
88
|
+
import { useSyncExternalStore, useMemo, useCallback as useCallback2 } from "react";
|
|
89
|
+
function useSubscription(subscriptionKey, ...params) {
|
|
90
|
+
const store = useStore();
|
|
91
|
+
const subscription = useMemo(
|
|
92
|
+
() => store.getSubscription(subscriptionKey, params),
|
|
93
|
+
[store, subscriptionKey, ...params]
|
|
94
|
+
// Spread params - React will handle equality
|
|
95
|
+
);
|
|
96
|
+
const getSnapshot = useCallback2(
|
|
97
|
+
() => store.query(subscription.key, subscription.params),
|
|
98
|
+
[store, subscription]
|
|
99
|
+
// Subscription object is stable, so this is stable too
|
|
100
|
+
);
|
|
101
|
+
const subscribe = useCallback2(
|
|
102
|
+
(onStoreChange) => {
|
|
103
|
+
return store.subscribe(
|
|
104
|
+
subscription.key,
|
|
105
|
+
subscription.params,
|
|
106
|
+
() => {
|
|
107
|
+
onStoreChange();
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
[store, subscription]
|
|
112
|
+
// Subscription object is stable, so this is stable too
|
|
113
|
+
);
|
|
114
|
+
return useSyncExternalStore(
|
|
115
|
+
subscribe,
|
|
116
|
+
getSnapshot,
|
|
117
|
+
getSnapshot
|
|
118
|
+
// Server snapshot (same as client for now)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
function createSubscriptionHook(subscriptionKey) {
|
|
122
|
+
return function useSubscriptionHook(...params) {
|
|
123
|
+
const store = useStore();
|
|
124
|
+
const subscription = useMemo(
|
|
125
|
+
() => store.getSubscription(subscriptionKey, params),
|
|
126
|
+
[store, subscriptionKey, ...params]
|
|
127
|
+
);
|
|
128
|
+
const getSnapshot = useCallback2(
|
|
129
|
+
() => store.query(subscription.key, subscription.params),
|
|
130
|
+
[store, subscription]
|
|
131
|
+
);
|
|
132
|
+
const subscribe = useCallback2(
|
|
133
|
+
(onStoreChange) => {
|
|
134
|
+
return store.subscribe(
|
|
135
|
+
subscription.key,
|
|
136
|
+
subscription.params,
|
|
137
|
+
() => {
|
|
138
|
+
onStoreChange();
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
[store, subscription]
|
|
143
|
+
);
|
|
144
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
StoreProvider,
|
|
149
|
+
createSubscriptionHook,
|
|
150
|
+
useDispatch,
|
|
151
|
+
useStore,
|
|
152
|
+
useStoreState,
|
|
153
|
+
useSubscription
|
|
154
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rplx/react",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "React bindings for Ripple state management",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
21
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=16.8.0"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@rplx/core": "^0.2.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/react": "^18.2.43",
|
|
32
|
+
"react": "^18.2.0",
|
|
33
|
+
"tsup": "^8.0.0",
|
|
34
|
+
"typescript": "^5.2.2"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"state-management",
|
|
38
|
+
"react",
|
|
39
|
+
"re-frame",
|
|
40
|
+
"ripple"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/anonymeye/ripple.git",
|
|
46
|
+
"directory": "packages/ripple-react"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/anonymeye/ripple/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/anonymeye/ripple#readme",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|