muya 1.0.3 → 2.0.0-beta.1
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 +194 -216
- package/cjs/index.js +1 -1
- package/esm/__tests__/create-async.test.js +1 -0
- package/esm/create.js +1 -1
- package/esm/index.js +1 -1
- package/esm/subscriber.js +1 -0
- package/esm/types.js +1 -1
- package/esm/use.js +1 -0
- package/esm/utils/__tests__/context.test.js +1 -0
- package/esm/utils/__tests__/is.test.js +1 -0
- package/esm/utils/__tests__/sub-memo.test.js +1 -0
- package/esm/utils/common.js +1 -0
- package/esm/utils/create-context.js +1 -0
- package/esm/utils/create-emitter.js +1 -0
- package/esm/utils/is.js +1 -0
- package/esm/utils/scheduler.js +1 -0
- package/esm/utils/shallow.js +1 -0
- package/esm/utils/sub-memo.js +1 -0
- package/package.json +1 -4
- package/packages/core/__tests__/bench.test.tsx +261 -0
- package/packages/core/__tests__/create-async.test.ts +88 -0
- package/packages/core/__tests__/create.test.tsx +107 -0
- package/packages/core/__tests__/use-async.test.tsx +44 -0
- package/packages/core/__tests__/use.test.tsx +76 -0
- package/packages/core/create.ts +67 -0
- package/packages/core/index.ts +4 -0
- package/packages/core/subscriber.ts +121 -0
- package/packages/core/types.ts +15 -0
- package/packages/core/use.ts +59 -0
- package/packages/core/utils/__tests__/context.test.ts +198 -0
- package/{src → packages/core/utils}/__tests__/is.test.ts +1 -30
- package/packages/core/utils/__tests__/sub-memo.test.ts +13 -0
- package/packages/core/utils/common.ts +48 -0
- package/packages/core/utils/create-context.ts +60 -0
- package/packages/core/utils/create-emitter.ts +53 -0
- package/{src → packages/core/utils}/is.ts +11 -13
- package/packages/core/utils/scheduler.ts +59 -0
- package/{src → packages/core/utils}/shallow.ts +3 -3
- package/packages/core/utils/sub-memo.ts +37 -0
- package/types/create.d.ts +14 -21
- package/types/index.d.ts +2 -4
- package/types/subscriber.d.ts +25 -0
- package/types/types.d.ts +9 -65
- package/types/use.d.ts +2 -0
- package/types/utils/common.d.ts +15 -0
- package/types/utils/create-context.d.ts +5 -0
- package/types/utils/create-emitter.d.ts +20 -0
- package/types/{is.d.ts → utils/is.d.ts} +5 -6
- package/types/utils/scheduler.d.ts +6 -0
- package/types/utils/sub-memo.d.ts +6 -0
- package/esm/__tests__/common.test.js +0 -1
- package/esm/__tests__/is.test.js +0 -1
- package/esm/__tests__/merge.test.js +0 -1
- package/esm/__tests__/types.test.js +0 -1
- package/esm/common.js +0 -1
- package/esm/create-base-state.js +0 -1
- package/esm/create-emitter.js +0 -1
- package/esm/create-getter-state.js +0 -1
- package/esm/is.js +0 -1
- package/esm/merge.js +0 -1
- package/esm/select.js +0 -1
- package/esm/shallow.js +0 -1
- package/esm/use-state-value.js +0 -1
- package/src/__tests__/common.test.ts +0 -63
- package/src/__tests__/create.test.tsx +0 -84
- package/src/__tests__/merge.test.ts +0 -78
- package/src/__tests__/state.test.tsx +0 -619
- package/src/__tests__/types.test.ts +0 -17
- package/src/common.ts +0 -60
- package/src/create-base-state.ts +0 -31
- package/src/create-emitter.ts +0 -24
- package/src/create-getter-state.ts +0 -18
- package/src/create.ts +0 -127
- package/src/index.ts +0 -6
- package/src/merge.ts +0 -38
- package/src/select.ts +0 -33
- package/src/types.ts +0 -94
- package/src/use-state-value.ts +0 -32
- package/types/common.d.ts +0 -17
- package/types/create-base-state.d.ts +0 -10
- package/types/create-emitter.d.ts +0 -7
- package/types/create-getter-state.d.ts +0 -6
- package/types/merge.d.ts +0 -4
- package/types/select.d.ts +0 -2
- package/types/use-state-value.d.ts +0 -10
- /package/esm/{__tests__ → utils/__tests__}/shallow.test.js +0 -0
- /package/{src → packages/core}/__tests__/test-utils.ts +0 -0
- /package/{src → packages/core/utils}/__tests__/shallow.test.ts +0 -0
- /package/types/{shallow.d.ts → utils/shallow.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,321 +1,299 @@
|
|
|
1
|
+
|
|
1
2
|
# Muya 🌀
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
Welcome to **Muya v2**—a state management library that makes managing state a breeze, focusing on simplicity and scalability for real-world applications.
|
|
3
5
|
|
|
4
6
|
[](https://github.com/samuelgja/muya/actions/workflows/build.yml)
|
|
5
|
-
[](https://github.com/samuelgja/muya/actions/workflows/code-check.yml)
|
|
6
8
|
[](https://bundlephobia.com/result?p=muya)
|
|
7
9
|
|
|
10
|
+
---
|
|
11
|
+
|
|
8
12
|
## 🚀 Features
|
|
9
|
-
- Easy State Creation: Kickstart your state management with simple and intuitive APIs.
|
|
10
|
-
- Selectors & Merges: Grab exactly what you need from your state and combine multiple states seamlessly.
|
|
11
|
-
- Deep Nesting Support: Handle complex state structures without breaking a sweat.
|
|
12
|
-
- Optimized Rendering: Prevent unnecessary re-renders
|
|
13
|
-
- TypeScript Ready: Fully typed for maximum developer sanity.
|
|
14
|
-
- Small Bundle Size: Lightweight and fast, no bloatware here.
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
- **Simple State Creation**: Easily create global state with an intuitive API.
|
|
15
|
+
- **Functional Derivations**: Derive / Compute new state using plain functions, embracing functional programming.
|
|
16
|
+
- **Async Support**: Async derived states work out of the box, with support for React Suspense.
|
|
17
|
+
- **Performance Optimizations**: WIP Batch state updates for optimized rendering.
|
|
18
|
+
- **TypeScript Support**: Fully typed for a better developer experience.
|
|
19
|
+
- **Small Bundle Size**: Lightweight and efficient—no unnecessary bloat.
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
bun add muya
|
|
20
|
-
```
|
|
21
|
-
or
|
|
22
|
-
```bash
|
|
23
|
-
yarn add muya
|
|
24
|
-
```
|
|
25
|
-
or
|
|
26
|
-
```bash
|
|
27
|
-
npm install muya
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## 📝 Quick Start
|
|
21
|
+
---
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
import { create } from 'muya'
|
|
23
|
+
Info: [Muya v2](https://medium.com/p/106de3d04c43)
|
|
34
24
|
|
|
35
|
-
|
|
25
|
+
### 💡 How It Works
|
|
26
|
+
Muya v2 uses its own custom context system to track dependencies and notify components when state changes. When you use a function that accesses state in your component, Muya tracks which states are used and re-renders the component when any of them change.
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
const counter = useCounter()
|
|
39
|
-
return <div onClick={() => useCounter.setState((prev) => prev + 1)}>{counter}</div>
|
|
40
|
-
}
|
|
41
|
-
```
|
|
28
|
+
This allows you to write derived state as plain functions, making your code more intuitive and closer to standard JavaScript. No need to define selectors or use special APIs—just use functions!
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
Sugar syntax above the `setState` method for partially updating the state.
|
|
45
|
-
```typescript
|
|
46
|
-
import { create } from 'muya'
|
|
30
|
+
---
|
|
47
31
|
|
|
48
|
-
|
|
32
|
+
## 📦 Installation
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// this will just partially update only the name field, it's sugar syntax for setState.
|
|
53
|
-
return <div onClick={() => useUser.updateState({ name: 'Nope' })}>{user.name}</div>
|
|
54
|
-
}
|
|
34
|
+
```bash
|
|
35
|
+
npm install muya
|
|
55
36
|
```
|
|
56
37
|
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
import { create } from 'muya'
|
|
60
|
-
|
|
61
|
-
const useUser = create({ name: 'John', age: 30 })
|
|
38
|
+
Or using Yarn:
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
|
|
40
|
+
```bash
|
|
41
|
+
yarn add muya
|
|
42
|
+
```
|
|
65
43
|
|
|
66
|
-
|
|
67
|
-
const name = useName()
|
|
68
|
-
return <div onClick={() => useUser.setState((prev) => ({ ...prev, name: 'Jane' }))}>{name}</div>
|
|
69
|
-
}
|
|
44
|
+
Or using Bun:
|
|
70
45
|
|
|
46
|
+
```bash
|
|
47
|
+
bun add muya
|
|
71
48
|
```
|
|
72
49
|
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { create, shallow, merge } from 'muya'
|
|
76
|
-
|
|
77
|
-
const useName = create(() => 'John')
|
|
78
|
-
const useAge = create(() => 30)
|
|
50
|
+
---
|
|
79
51
|
|
|
80
|
-
|
|
52
|
+
## 📝 Quick Start
|
|
81
53
|
|
|
82
|
-
|
|
83
|
-
const fullName = useFullName()
|
|
84
|
-
return <div onClick={() => useName.setState((prev) => 'Jane')}>{fullName}</div>
|
|
85
|
-
}
|
|
86
|
-
```
|
|
54
|
+
Here's how to get started with **Muya v2**:
|
|
87
55
|
|
|
88
|
-
###
|
|
89
|
-
This methods are useful for handling async data fetching and lazy loading via React Suspense.
|
|
56
|
+
### Creating State
|
|
90
57
|
|
|
91
|
-
#### Immediate Promise resolution
|
|
92
58
|
```typescript
|
|
93
59
|
import { create } from 'muya';
|
|
94
|
-
// state will try to resolve the promise immediately, can hit the suspense boundary
|
|
95
|
-
const counterState = create(Promise.resolve(0));
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
const counter = counterState();
|
|
99
|
-
return (
|
|
100
|
-
<div onClick={() => counterState.setState((prev) => prev + 1)}>
|
|
101
|
-
{counter}
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
61
|
+
const counter = create(0);
|
|
105
62
|
```
|
|
106
63
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
64
|
+
### Using State in Components
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import React from 'react';
|
|
68
|
+
import { use } from 'muya';
|
|
112
69
|
|
|
113
70
|
function Counter() {
|
|
114
|
-
const
|
|
71
|
+
const count = use(counter);
|
|
72
|
+
|
|
115
73
|
return (
|
|
116
|
-
<div
|
|
117
|
-
{counter}
|
|
74
|
+
<div>
|
|
75
|
+
<button onClick={() => counter.set((prev) => prev + 1)}>
|
|
76
|
+
Increment
|
|
77
|
+
</button>
|
|
78
|
+
<p>Count: {count}</p>
|
|
118
79
|
</div>
|
|
119
80
|
);
|
|
120
81
|
}
|
|
121
82
|
```
|
|
122
83
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
### `create`
|
|
84
|
+
---
|
|
126
85
|
|
|
127
|
-
|
|
86
|
+
## 📚 Examples
|
|
128
87
|
|
|
129
|
-
|
|
130
|
-
function create<T>(defaultState: T, options?: StateOptions<T>): StateSetter<T>;
|
|
131
|
-
```
|
|
88
|
+
### Deriving / Selecting State with Functions
|
|
132
89
|
|
|
133
|
-
|
|
90
|
+
You can derive new state using plain functions:
|
|
134
91
|
|
|
135
92
|
```typescript
|
|
136
|
-
const
|
|
93
|
+
const counter = create(0);
|
|
94
|
+
|
|
95
|
+
function doubled() {
|
|
96
|
+
return counter() * 2;
|
|
97
|
+
}
|
|
137
98
|
```
|
|
138
99
|
|
|
139
|
-
|
|
100
|
+
Use it in a component:
|
|
140
101
|
|
|
141
|
-
|
|
102
|
+
```tsx
|
|
103
|
+
function DoubledCounter() {
|
|
104
|
+
const value = use(doubled);
|
|
142
105
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const userState = create({ name: 'John', age: 30 });
|
|
146
|
-
// Direct selection outside the component, is useful for accessing the slices of the state in multiple components
|
|
147
|
-
const userAgeState = userState.select((user) => user.age);
|
|
106
|
+
return <p>Doubled Count: {value}</p>;
|
|
107
|
+
}
|
|
148
108
|
```
|
|
149
109
|
|
|
150
|
-
###
|
|
151
|
-
Merges any number states into a single state.
|
|
152
|
-
```typescript
|
|
153
|
-
const useName = create(() => 'John');
|
|
154
|
-
const useAge = create(() => 30);
|
|
155
|
-
const useFullName = merge([useName, useAge], (name, age) => `${name} and ${age}`);
|
|
156
|
-
```
|
|
110
|
+
### Complex Derivations
|
|
157
111
|
|
|
158
|
-
|
|
159
|
-
Sets the state to a new value or a function that returns a new value.
|
|
112
|
+
Create more complex derived states by composing functions:
|
|
160
113
|
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
userState.setState({ name: 'Jane' });
|
|
164
|
-
```
|
|
114
|
+
```tsx
|
|
115
|
+
import { create, use } from 'muya';
|
|
165
116
|
|
|
166
|
-
|
|
167
|
-
|
|
117
|
+
const state1 = create(0);
|
|
118
|
+
const state2 = create(0);
|
|
119
|
+
const state3 = create(0);
|
|
168
120
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
```
|
|
121
|
+
function sum() {
|
|
122
|
+
return state1() + state2() + state3();
|
|
123
|
+
}
|
|
173
124
|
|
|
174
|
-
|
|
175
|
-
|
|
125
|
+
function multiply() {
|
|
126
|
+
return sum() * 2; // Example multiplier
|
|
127
|
+
}
|
|
176
128
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
```
|
|
129
|
+
function isOdd() {
|
|
130
|
+
return multiply() % 2 === 1;
|
|
131
|
+
}
|
|
181
132
|
|
|
182
|
-
|
|
183
|
-
|
|
133
|
+
function App() {
|
|
134
|
+
const isOddValue = use(isOdd);
|
|
184
135
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
136
|
+
return (
|
|
137
|
+
<div>
|
|
138
|
+
<button onClick={() => state1.set((c) => c + 1)}>Increment Counter 1</button>
|
|
139
|
+
<button onClick={() => state2.set((c) => c + 1)}>Increment Counter 2</button>
|
|
140
|
+
<button onClick={() => state3.set((c) => c + 1)}>Increment Counter 3</button>
|
|
141
|
+
<p>Is ODD: {isOddValue ? 'Yes' : 'No'}</p>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
189
145
|
```
|
|
190
146
|
|
|
191
|
-
### `subscribe`
|
|
192
|
-
Subscribes to the state changes.
|
|
193
147
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const unsubscribe = userState.subscribe((state) => console.log(state));
|
|
197
|
-
```
|
|
148
|
+
### Derive state with parameters
|
|
149
|
+
```tsx
|
|
198
150
|
|
|
199
|
-
|
|
151
|
+
import React, { useCallback, useState } from 'react';
|
|
152
|
+
import { create, use } from 'muya';
|
|
200
153
|
|
|
201
|
-
|
|
154
|
+
const counter1 = create(0);
|
|
155
|
+
const counter2 = create(0);
|
|
202
156
|
|
|
203
|
-
|
|
204
|
-
|
|
157
|
+
function multipliedSum(multiplier: number) {
|
|
158
|
+
return (counter1() + counter2()) * multiplier;
|
|
159
|
+
}
|
|
205
160
|
|
|
206
|
-
|
|
207
|
-
const
|
|
161
|
+
function MultipliedCounter() {
|
|
162
|
+
const [multiplier, setMultiplier] = useState(1);
|
|
163
|
+
// make sure to use useCallback to memoize the function
|
|
164
|
+
const multiply = useCallback(() => multipliedSum(multiplier), [multiplier]);
|
|
165
|
+
const result = use(multiply);
|
|
208
166
|
|
|
209
|
-
function Counter() {
|
|
210
|
-
const counter = counterState();
|
|
211
167
|
return (
|
|
212
|
-
<div
|
|
213
|
-
{
|
|
168
|
+
<div>
|
|
169
|
+
<button onClick={() => counter1.set((c) => c + 1)}>
|
|
170
|
+
Increment Counter 1
|
|
171
|
+
</button>
|
|
172
|
+
<button onClick={() => counter2.set((c) => c + 1)}>
|
|
173
|
+
Increment Counter 2
|
|
174
|
+
</button>
|
|
175
|
+
<button onClick={() => setMultiplier((m) => m + 1)}>
|
|
176
|
+
Increment Multiplier
|
|
177
|
+
</button>
|
|
178
|
+
<p>Result: {result}</p>
|
|
214
179
|
</div>
|
|
215
180
|
);
|
|
216
181
|
}
|
|
217
182
|
```
|
|
218
183
|
|
|
219
|
-
|
|
184
|
+
### Async derives State
|
|
220
185
|
|
|
221
|
-
```
|
|
186
|
+
```tsx
|
|
222
187
|
import { create } from 'muya';
|
|
223
188
|
|
|
224
|
-
|
|
225
|
-
|
|
189
|
+
const counter = create(1);
|
|
190
|
+
|
|
191
|
+
async function fetchData() {
|
|
192
|
+
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${counter()}`);
|
|
193
|
+
return response.json();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// make sure this component is wrapped in a <Suspense> component
|
|
197
|
+
function Component() {
|
|
198
|
+
const data = use(fetchData);
|
|
226
199
|
|
|
227
|
-
function Counter() {
|
|
228
|
-
const counter = counterState();
|
|
229
200
|
return (
|
|
230
|
-
<div
|
|
231
|
-
{counter}
|
|
201
|
+
<div>
|
|
202
|
+
<button onClick={() => counter.set((prev) => prev + 1)}>Increment</button>
|
|
203
|
+
<p>{JSON.stringify(data)}</p>
|
|
232
204
|
</div>
|
|
233
205
|
);
|
|
234
206
|
}
|
|
207
|
+
|
|
208
|
+
|
|
235
209
|
```
|
|
210
|
+
---
|
|
236
211
|
|
|
237
|
-
#### Promise Rejection Handling
|
|
238
212
|
|
|
239
|
-
```typescript
|
|
240
|
-
import { create } from 'muya';
|
|
241
213
|
|
|
242
|
-
// State will reject the promise
|
|
243
|
-
const counterState = create(Promise.reject('Error occurred'));
|
|
244
214
|
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const counter = counterState();
|
|
248
|
-
return <div>{counter}</div>;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
return <div>Error: {error}</div>;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
```
|
|
215
|
+
## 📖 Documentation
|
|
254
216
|
|
|
255
|
-
####
|
|
217
|
+
#### `create`
|
|
256
218
|
|
|
257
219
|
```typescript
|
|
258
|
-
|
|
220
|
+
function create<T>(initialState: T): State<T>;
|
|
221
|
+
```
|
|
259
222
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
223
|
+
Create return state block or atom, setting a new value to the state will trigger a re-render of all components using the state.
|
|
224
|
+
```typescript
|
|
225
|
+
const counter = create(0);
|
|
226
|
+
// set new value
|
|
227
|
+
counter.set(1);
|
|
228
|
+
```
|
|
264
229
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return <div>Error: {error.message}</div>;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
230
|
+
Get the state value outside of react
|
|
231
|
+
```typescript
|
|
232
|
+
const value = counter();
|
|
233
|
+
// call it like a function
|
|
234
|
+
console.log(value()); // 1
|
|
273
235
|
```
|
|
274
236
|
|
|
275
|
-
|
|
237
|
+
Each state created by create has the following properties and methods:
|
|
276
238
|
|
|
239
|
+
- id: number: Unique identifier for the state.
|
|
240
|
+
- (): T: The state is callable to get the current value.
|
|
241
|
+
- set(value: T | ((prev: T) => T)): Update the state value.
|
|
242
|
+
- listen(listener: (value: T) => void): () => void: Subscribe to state changes.
|
|
243
|
+
|
|
244
|
+
#### `use` hook
|
|
245
|
+
Use hook to get state value in react component
|
|
277
246
|
```typescript
|
|
278
|
-
|
|
247
|
+
function use<T>(state: State<T> | (() => T)): T;
|
|
248
|
+
```
|
|
249
|
+
example
|
|
250
|
+
```tsx
|
|
251
|
+
const counter = create(0);
|
|
279
252
|
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
// this will abort current promise and set the state to 10
|
|
283
|
-
counterState.setState(10);
|
|
284
|
-
function Counter() {
|
|
285
|
-
const counter = counterState();
|
|
286
|
-
return (
|
|
287
|
-
<div onClick={() => counterState.setState((prev) => prev + 1)}>
|
|
288
|
-
{counter}
|
|
289
|
-
</div>
|
|
290
|
-
);
|
|
291
|
-
}
|
|
253
|
+
// in react component
|
|
254
|
+
const count = use(counter);
|
|
292
255
|
```
|
|
293
256
|
|
|
257
|
+
Also custom selector to avoid re-rendering the app
|
|
258
|
+
```tsx
|
|
259
|
+
const doubled = create({doubled:true});
|
|
260
|
+
// in react component
|
|
261
|
+
const value = use(doubled, (state) => state.doubled);
|
|
262
|
+
```
|
|
294
263
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
### ⚠️ Notes
|
|
269
|
+
Memoization: When using derived functions with parameters, you should memoize them using useCallback to prevent unnecessary re-renders and prevent function calls.
|
|
270
|
+
```tsx
|
|
271
|
+
Copy code
|
|
272
|
+
const multiply = useCallback(() => multipliedSum(multiplier), [multiplier]);
|
|
300
273
|
```
|
|
301
|
-
|
|
274
|
+
Async Functions: Async functions used in use should handle errors appropriately and may need to use React's Suspense for loading states.
|
|
275
|
+
Also keep in note, setting new state in async derives, will cancel the pending previous one in the queue.
|
|
302
276
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
277
|
+
```tsx
|
|
278
|
+
Copy code
|
|
279
|
+
function App() {
|
|
280
|
+
return (
|
|
281
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
282
|
+
<DataComponent />
|
|
283
|
+
</Suspense>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
311
286
|
```
|
|
312
287
|
|
|
313
|
-
|
|
314
|
-
Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before submitting a pull request.
|
|
288
|
+
Error Handling: If an async function throws an error, you can catch it using React's error boundaries.
|
|
315
289
|
|
|
316
|
-
|
|
290
|
+
### 🤖 Contributing
|
|
291
|
+
Contributions are welcome! Please read the contributing guidelines before submitting a pull request.
|
|
292
|
+
|
|
293
|
+
### 🧪 Testing
|
|
317
294
|
Muya comes with a robust testing suite. Check out the state.test.tsx for examples on how to write your own tests.
|
|
318
295
|
|
|
319
|
-
## 📜 License
|
|
320
296
|
|
|
321
|
-
|
|
297
|
+
### 🙏 Acknowledgments
|
|
298
|
+
Special thanks to reddit to motivate me for v2 :D
|
|
299
|
+
|
package/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var h=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var K=(e,t)=>{for(var r in t)h(e,r,{get:t[r],enumerable:!0})},Y=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of X(t))!N.call(e,n)&&n!==r&&h(e,n,{get:()=>t[n],enumerable:!(o=q(t,n))||o.enumerable});return e};var G=e=>Y(h({},"__esModule",{value:!0}),e);var J={};K(J,{EMPTY_SELECTOR:()=>w,create:()=>U,shallow:()=>j,use:()=>H});module.exports=G(J);var w=e=>e;function p(e){return e instanceof Promise}function g(e){return typeof e=="function"}function R(e){return e instanceof Map}function C(e){return e instanceof Set}function P(e){return Array.isArray(e)}function b(e,t){return e===t?!0:!!Object.is(e,t)}function A(e){return typeof e=="function"}function I(e){return e instanceof DOMException&&e.name==="StateAbortError"}function v(e){return e instanceof Error&&e.name!=="StateAbortError"}function l(e){return e===void 0}function M(e,t){t&&t.abort();let r=new AbortController,{signal:o}=r;return{promise:new Promise((i,s)=>{o.addEventListener("abort",()=>{s(new DOMException("Promise was aborted","StateAbortError"))}),e.then(i).catch(s)}),controller:r}}var W=0;function y(){return W++}function E(e,t=(r,o)=>r===o){if(!l(e.current)){if(!l(e.previous)&&t(e.current,e.previous))return!1;e.previous=e.current}return!0}function S(e,t){let r=new Set,o=[];return{clear:()=>{for(let n of o)n();r.clear()},subscribe:n=>(r.add(n),()=>{r.delete(n)}),emit:(...n)=>{for(let i of r)i(...n)},contains:n=>r.has(n),getSnapshot:e,getInitialSnapshot:t,getSize:()=>r.size,subscribeToOtherEmitter(n){let i=n.subscribe(()=>{this.emit()});o.push(i)}}}function D(e){let t=new Set,{onResolveItem:r,onFinish:o}=e,n=performance.now(),i=!1;function s(){let c=performance.now(),f=c-n,{size:m}=t;if(f<.2&&m>0&&m<10){n=c,u();return}i||(i=!0,Promise.resolve().then(()=>{i=!1,n=performance.now(),u()}))}function u(){if(t.size!==0){for(let c of t)r&&r(c),t.delete(c);if(t.size>0){s();return}o()}}function a(c){t.add(c),s()}return a}var $=Symbol("_");function V(e){let t=[];function r(){if(t.length===0)return e;let i=t.at(-1);return i===$?e:i}function o(i,s){t.push(i);let u=s();return p(u)?(async()=>{try{return await u}finally{t.pop()}})():(t.pop(),u)}function n(i){let s=r();return()=>{t.push(s);let u=i();return p(u)?(async()=>{try{return await u}finally{t.pop()}})():(t.pop(),u)}}return{run:o,use:r,wrap:n}}var F=V(void 0);function L(e){let t=[],r={},o={},n=!1,i=S(()=>n?o.current:(n=!0,c()),()=>(n=!0,c()));async function s(){E(o,b)&&(r.controller&&r.controller.abort(),o.current=c(),i.emit())}let u=y(),a={addEmitter(f){let m=f.subscribe(s);t.push(m)},id:u,sub:s},c=function(){let f=F.run(a,e);if(p(f)){let{controller:m,promise:O}=M(f,r.controller);r.controller=m,O?.then(d=>{o.current=d,i.emit()}).catch(d=>{if(!I(d))throw d});let k=O;return o.current=k,k}return o.current=f,f};return c.emitter=i,c.destroy=function(){for(let f of t)f();i.clear()},c.id=u,c.listen=function(f){return i.subscribe(()=>{let m=o.current;if(l(m))throw new Error("The value is undefined");f(m)})},c.abort=function(){r.controller&&r.controller.abort()},c}function U(e,t=b){let r={};function o(){return l(r.current)&&(r.current=g(e)?e():e),r.current}function n(u){let a=o();r.current=A(u)?u(a):u}let i=D({onFinish(){r.current=o(),E(r,t)&&s.emitter.emit()},onResolveItem:n}),s=function(){let u=o(),a=F.use();return a&&!s.emitter.contains(a.sub)&&a.addEmitter(s.emitter),u};return s.listen=function(u){return s.emitter.subscribe(()=>{let a=r.current;if(l(a))throw new Error("The value is undefined");u(a)})},s.emitter=S(()=>s()),s.id=y(),s.set=i,s}var T=require("react");var z=require("react");var x=new WeakMap;function _(e){return{call(){let t=x.get(e);if(t)return t.count++,t.returnType;let o={count:1,returnType:L(e)};return x.set(e,o),o.returnType},destroy(){let t=x.get(e);t&&(t.count--,t.count===0&&(t.returnType.destroy(),x.delete(e)))}}}function H(e,t=w){let r=_(e),o=r.call(),n=o.emitter.getInitialSnapshot??o.emitter.getSnapshot;(0,T.useEffect)(()=>r.destroy,[e]);let i=(0,z.useSyncExternalStore)(o.emitter.subscribe,()=>t(o.emitter.getSnapshot()),()=>t(n()));if((0,T.useDebugValue)(i),p(i))throw i;if(v(i))throw r.destroy(),i;return i}function j(e,t){if(e==t)return!0;if(typeof e!="object"||e==null||typeof t!="object"||t==null)return!1;if(R(e)&&R(t)){if(e.size!==t.size)return!1;for(let[n,i]of e)if(!Object.is(i,t.get(n)))return!1;return!0}if(C(e)&&C(t)){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}if(P(e)&&P(t)){if(e.length!==t.length)return!1;for(let[n,i]of e.entries())if(!Object.is(i,t[n]))return!1;return!0}let r=Object.keys(e),o=Object.keys(t);if(r.length!==o.length)return!1;for(let n of r)if(!Object.prototype.hasOwnProperty.call(t,n)||!Object.is(e[n],t[n]))return!1;return!0}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{create as r}from"../create";import{waitFor as c}from"@testing-library/react";import{longPromise as m}from"./test-utils";import{isPromise as u}from"../utils/is";import{subscriber as n}from"../subscriber";describe("create",()=>{it("should subscribe to context and notified it with parameters",async()=>{const t=r(1),i=r(2);function s(){return t()+i()}async function a(p){return t()+i()+s()+p}let o=0;const e=n(()=>a(10));expect(u(e.emitter.getSnapshot())).toBe(!0),e.listen(async()=>{o++}),expect(o).toBe(0),expect(await e()).toBe(16),t.set(2),await c(async()=>{}),expect(await e()).toBe(18),expect(o).toBe(4)}),it("should async subscribe to context and notified it",async()=>{const t=r(1),i=r(Promise.resolve(2));async function s(){return await m(),t()+await i()}async function a(){return t()+await i()+await s()}let o=0;const e=n(a);e.listen(()=>{o++}),e(),expect(t.emitter.getSize()).toBe(1),expect(i.emitter.getSize()).toBe(1),expect(e.emitter.getSize()).toBe(1),t.set(2),await c(async()=>{expect(await e()).toBe(8),expect(o).toBe(5)}),i.set(3),await c(async()=>{expect(await e()).toBe(10),expect(o).toBe(10)}),expect(t.emitter.getSize()).toBe(1),expect(i.emitter.getSize()).toBe(1),expect(e.emitter.getSize()).toBe(1),e.destroy(),expect(t.emitter.getSize()).toBe(0),expect(i.emitter.getSize()).toBe(0),expect(e.emitter.getSize()).toBe(0)})});
|
package/esm/create.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{canUpdate as m,generateId as l}from"./utils/common";import{createEmitter as f}from"./utils/create-emitter";import{isEqualBase as T,isFunction as d,isSetValueFunction as p,isUndefined as o}from"./utils/is";import{createScheduler as S}from"./utils/scheduler";import{context as h}from"./subscriber";function y(i,u=T){const r={};function a(){return o(r.current)&&(r.current=d(i)?i():i),r.current}function c(n){const t=a();r.current=p(n)?n(t):n}const s=S({onFinish(){r.current=a(),m(r,u)&&e.emitter.emit()},onResolveItem:c}),e=function(){const n=a(),t=h.use();return t&&!e.emitter.contains(t.sub)&&t.addEmitter(e.emitter),n};return e.listen=function(n){return e.emitter.subscribe(()=>{const t=r.current;if(o(t))throw new Error("The value is undefined");n(t)})},e.emitter=f(()=>e()),e.id=l(),e.set=s,e}export{y as create};
|
package/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./types";import{create as t}from"./create";import{
|
|
1
|
+
export*from"./types";import{create as t}from"./create";import{use as m}from"./use";import{shallow as x}from"./utils/shallow";export{t as create,x as shallow,m as use};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cancelablePromise as p,canUpdate as T,generateId as y}from"./utils/common";import{createContext as F}from"./utils/create-context";import{createEmitter as x}from"./utils/create-emitter";import{isAbortError as h,isEqualBase as C,isPromise as R,isUndefined as E}from"./utils/is";const S=F(void 0);function k(d){const u=[],n={},r={};let s=!1;const o=x(()=>s?r.current:(s=!0,t()),()=>(s=!0,t()));async function a(){T(r,C)&&(n.controller&&n.controller.abort(),r.current=t(),o.emit())}const l=y(),f={addEmitter(e){const i=e.subscribe(a);u.push(i)},id:l,sub:a},t=function(){const e=S.run(f,d);if(R(e)){const{controller:i,promise:m}=p(e,n.controller);n.controller=i,m?.then(c=>{r.current=c,o.emit()}).catch(c=>{if(!h(c))throw c});const b=m;return r.current=b,b}return r.current=e,e};return t.emitter=o,t.destroy=function(){for(const e of u)e();o.clear()},t.id=l,t.listen=function(e){return o.subscribe(()=>{const i=r.current;if(E(i))throw new Error("The value is undefined");e(i)})},t.abort=function(){n.controller&&n.controller.abort()},t}export{S as context,k as subscriber};
|
package/esm/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const t=e=>e;export{t as EMPTY_SELECTOR};
|
package/esm/use.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useDebugValue as u,useEffect as s,useRef as m}from"react";import{EMPTY_SELECTOR as a}from"./types";import{isAnyOtherError as c,isPromise as T}from"./utils/is";import{useSyncExternalStore as d}from"react";import{subMemo as p}from"./utils/sub-memo";const f=10,E=3;function b(t){const e=m({renders:0,startTime:performance.now()});s(()=>{e.current.renders++,!(performance.now()-e.current.startTime<f)&&(e.current.renders<E||(e.current.startTime=performance.now(),e.current.renders=0,console.warn(`Function ${t.name.length>0?t.name:t} seems to be not memoized, wrap the function to the useCallback or use global defined functions.`)))},[t])}function y(t,e=a){const n=p(t),o=n.call(),i=o.emitter.getInitialSnapshot??o.emitter.getSnapshot;s(()=>n.destroy,[t]);const r=d(o.emitter.subscribe,()=>e(o.emitter.getSnapshot()),()=>e(i()));if(u(r),T(r))throw r;if(c(r))throw n.destroy(),r;return r}export{y as use};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createContext as c}from"../create-context";import{longPromise as r}from"../../__tests__/test-utils";describe("context",()=>{it("should check context",()=>{const e=c({name:"John Doe"}),t=()=>{e.run({name:"Jane Doe"},()=>{expect(e.use()).toEqual({name:"Jane Doe"})})};expect(e.use()).toEqual({name:"John Doe"}),t(),expect(e.use()).toEqual({name:"John Doe"})}),it("should test async context",e=>{const t=c("empty"),o=async()=>new Promise(u=>setTimeout(u,10));t.run("outer",()=>{expect(t.use()).toEqual("outer"),setTimeout(t.wrap(async()=>{try{await o(),expect(t.use()).toEqual("outer"),s()}catch(u){e(u)}}),10),t.run("inner",()=>{expect(t.use()).toEqual("inner"),setTimeout(t.wrap(()=>{try{expect(t.use()).toEqual("inner"),s()}catch(u){e(u)}}),10)}),expect(t.use()).toEqual("outer")});let n=0;function s(){n+=1,n===2&&e()}}),it("should test async nested context",e=>{const t=c(0);t.run(1,()=>{expect(t.use()).toEqual(1),t.run(2,()=>{t.run(3,()=>{expect(t.use()).toEqual(3),setTimeout(t.wrap(()=>{expect(t.use()).toEqual(3)}),10),expect(t.use()).toEqual(3)}),setTimeout(t.wrap(()=>{expect(t.use()).toEqual(2)}),10),expect(t.use()).toEqual(2),t.run(3,()=>{expect(t.use()).toEqual(3),setTimeout(t.wrap(()=>{expect(t.use()).toEqual(3),t.run(4,()=>{expect(t.use()).toEqual(4),setTimeout(t.wrap(()=>{expect(t.use()).toEqual(4),e()}),10),expect(t.use()).toEqual(4)})}),10),expect(t.use()).toEqual(3)}),expect(t.use()).toEqual(2)}),expect(t.use()).toEqual(1)}),expect(t.use()).toEqual(0)}),it("should stress test context with async random code",async()=>{const t=c(0);for(let n=0;n<1e4;n++)t.run(n,()=>{expect(t.use()).toEqual(n)});const o=[];for(let n=0;n<1e4;n++)t.run(n,()=>{expect(t.use()).toEqual(n);const s=new Promise(u=>{setTimeout(t.wrap(()=>{expect(t.use()).toEqual(n),u(n)}),Math.random()*100)});o.push(s)});await Promise.all(o)}),it("should-test-default-value-with-ctx",async()=>{const e=c({counter:1});e.run({counter:10},async()=>{await r(10),expect(e.use()?.counter).toBe(10)}),e.run({counter:12},()=>{expect(e.use()?.counter).toBe(12)})}),it("should test nested context",()=>{const t=c({count:0});function o(){const n=t.use();expect(n?.count).toBe(0),t.run({count:1},()=>{const s=t.use();expect(s?.count).toBe(1),t.run({count:2},()=>{const u=t.use();expect(u?.count).toBe(2)})})}o(),o()}),it("should test nested context with async when promise is returned, but not waited",async()=>{const e=c({count:0});async function t(){await r(10);const n=e.use();expect(n?.count).toBe(1)}async function o(){await t(),e.wrap(t);const n=e.use();expect(n?.count).toBe(1)}e.run({count:1},o)})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Abort as a}from"../common";import{isPromise as e,isFunction as r,isSetValueFunction as t,isMap as o,isSet as s,isArray as u,isEqualBase as i,isAbortError as n}from"../is";describe("isPromise",()=>{it("should return true for a Promise",()=>{expect(e(Promise.resolve())).toBe(!0)}),it("should return false for a non-Promise",()=>{expect(e(123)).toBe(!1)})}),describe("isFunction",()=>{it("should return true for a function",()=>{expect(r(()=>{})).toBe(!0)}),it("should return false for a non-function",()=>{expect(r(123)).toBe(!1)})}),describe("isSetValueFunction",()=>{it("should return true for a function",()=>{expect(t(()=>{})).toBe(!0)}),it("should return false for a non-function",()=>{expect(t(123)).toBe(!1)})}),describe("isMap",()=>{it("should return true for a Map",()=>{expect(o(new Map)).toBe(!0)}),it("should return false for a non-Map",()=>{expect(o(123)).toBe(!1)})}),describe("isSet",()=>{it("should return true for a Set",()=>{expect(s(new Set)).toBe(!0)}),it("should return false for a non-Set",()=>{expect(s(123)).toBe(!1)})}),describe("isArray",()=>{it("should return true for an array",()=>{expect(u([])).toBe(!0)}),it("should return false for a non-array",()=>{expect(u(123)).toBe(!1)})}),describe("isEqualBase",()=>{it("should return true for equal values",()=>{expect(i(1,1)).toBe(!0)}),it("should return false for non-equal values",()=>{expect(i(1,2)).toBe(!1)})}),describe("isAbortError",()=>{it("should return true for an AbortError",()=>{expect(n(new DOMException("",a.Error))).toBe(!0)}),it("should return false for a non-AbortError",()=>{expect(n(new DOMException("","Error"))).toBe(!1)})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{subMemo as t}from"../sub-memo";describe("memo-fn",()=>{it("should create memo fn",()=>{function e(){return!0}const o=t(e);expect(o.call().emitter).toBeDefined()})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isUndefined as i}from"./is";var a=(e=>(e.Error="StateAbortError",e))(a||{});function m(r,e){e&&e.abort();const o=new AbortController,{signal:t}=o;return{promise:new Promise((l,n)=>{t.addEventListener("abort",()=>{n(new DOMException("Promise was aborted","StateAbortError"))}),r.then(l).catch(n)}),controller:o}}let s=0;function p(){return s++}function b(r,e=(o,t)=>o===t){if(!i(r.current)){if(!i(r.previous)&&e(r.current,r.previous))return!1;r.previous=r.current}return!0}export{a as Abort,b as canUpdate,m as cancelablePromise,p as generateId};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isPromise as i}from"./is";const a=Symbol("_");function f(s){const t=[];function o(){if(t.length===0)return s;const e=t.at(-1);return e===a?s:e}function u(e,n){t.push(e);const r=n();return i(r)?(async()=>{try{return await r}finally{t.pop()}})():(t.pop(),r)}function c(e){const n=o();return()=>{t.push(n);const r=e();return i(r)?(async()=>{try{return await r}finally{t.pop()}})():(t.pop(),r)}}return{run:u,use:o,wrap:c}}export{f as createContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function o(n,s){const t=new Set,i=[];return{clear:()=>{for(const e of i)e();t.clear()},subscribe:e=>(t.add(e),()=>{t.delete(e)}),emit:(...e)=>{for(const r of t)r(...e)},contains:e=>t.has(e),getSnapshot:n,getInitialSnapshot:s,getSize:()=>t.size,subscribeToOtherEmitter(e){const r=e.subscribe(()=>{this.emit()});i.push(r)}}}export{o as createEmitter};
|
package/esm/utils/is.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Abort as e}from"./common";function o(n){return n instanceof Promise}function i(n){return typeof n=="function"}function u(n){return n instanceof Map}function s(n){return n instanceof Set}function a(n){return Array.isArray(n)}function f(n,r){return n===r?!0:!!Object.is(n,r)}function c(n){return typeof n=="function"}function p(n){return n instanceof DOMException&&n.name===e.Error}function k(n){return n instanceof Error&&n.name!==e.Error}function w(n){return n===void 0}export{p as isAbortError,k as isAnyOtherError,a as isArray,f as isEqualBase,i as isFunction,u as isMap,o as isPromise,s as isSet,c as isSetValueFunction,w as isUndefined};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const T=.2,d=10,E=0;function S(f){const n=new Set,{onResolveItem:r,onFinish:a}=f;let o=performance.now(),t=!1;function s(){const e=performance.now(),u=e-o,{size:c}=n;if(u<.2&&c>0&&c<10){o=e,i();return}t||(t=!0,Promise.resolve().then(()=>{t=!1,o=performance.now(),i()}))}function i(){if(n.size!==0){for(const e of n)r&&r(e),n.delete(e);if(n.size>0){s();return}a()}}function l(e){n.add(e),s()}return l}export{S as createScheduler};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isArray as f,isMap as o,isSet as i}from"./is";function b(r,t){if(r==t)return!0;if(typeof r!="object"||r==null||typeof t!="object"||t==null)return!1;if(o(r)&&o(t)){if(r.size!==t.size)return!1;for(const[e,n]of r)if(!Object.is(n,t.get(e)))return!1;return!0}if(i(r)&&i(t)){if(r.size!==t.size)return!1;for(const e of r)if(!t.has(e))return!1;return!0}if(f(r)&&f(t)){if(r.length!==t.length)return!1;for(const[e,n]of r.entries())if(!Object.is(n,t[e]))return!1;return!0}const s=Object.keys(r),c=Object.keys(t);if(s.length!==c.length)return!1;for(const e of s)if(!Object.prototype.hasOwnProperty.call(t,e)||!Object.is(r[e],t[e]))return!1;return!0}export{b as shallow};
|