Skip to content

Commit 8c0fbc6

Browse files
committed
Add support for silent state updates and immediate events
1 parent c8bc703 commit 8c0fbc6

File tree

9 files changed

+115
-41
lines changed

9 files changed

+115
-41
lines changed

.npmignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
dist/
2-
node_modules/
2+
node_modules/
3+
CHANGELOG.md

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"insertPragma": false,
66
"jsxBracketSameLine": false,
77
"jsxSingleQuote": true,
8-
"printWidth": 80,
8+
"printWidth": 100,
99
"proseWrap": "always",
1010
"quoteProps": "as-needed",
1111
"requirePragma": false,

CHANGELOG.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here. The format is based on
4+
[keep a changelog](https://keepachangelog.com/en/1.1.0/).
5+
6+
## [1.0.7] - 2025-08-30
7+
8+
#### Added
9+
10+
- Include `CHANGELOG.md` as part of Github push, exclude from npm push.
11+
- Added support for silent state updates via `options.silent` for setter, and support for firing a
12+
state update event directly via `options.fireImmediately` during subscription.
13+
- Separated type definitions into `StoreSetOptions` and `StoreSubscriptionOptions`
14+
15+
#### Changes
16+
17+
- Renamed `useBeacon` to `useValue`
18+
19+
## [1.0.6] - 2025-08-27
20+
21+
#### Changed
22+
23+
- Renamed library to `react-orba`
24+
- Modify README.md
25+
- 🟡 **TODO**: Rename `useBeacon` and it's implementations.
26+
27+
## [1.0.5] - 2025-08-26 [open-sourced]
28+
29+
#### Added
30+
31+
- Added .npmignore
32+
33+
## [1.0.4] - 2025-07-14
34+
35+
#### Added
36+
37+
- Prettier for linting
38+
39+
#### Changed
40+
41+
- Prepare command `"prepare": "npx prettier . --write && npm run build",` now formats before build.
42+
- Fixed issue with `useBeacon` that would break for non-primitive types.
43+
44+
## [1.0.3] - 2025-06-10
45+
46+
#### Added
47+
48+
- Helper function `mapSelectedStateToGlobalState<T, U>` added to map updated state slices with
49+
global state.
50+
51+
#### Changed
52+
53+
- `useBeacon` uses a state setter to merge updated states along with `unstable_batchedUpdates`.
54+
- 🔴 **VERSION DEPENDENCY**: React 18+
55+
56+
## [1.0.2] - 2025-05-28
57+
58+
#### Added
59+
60+
- Hook now uses `useSyncExternalStore` for batched updates.
61+
62+
#### Changed
63+
64+
- `useGlobalState` renamed to `useBeacon`
65+
- `useBeacon` returns a tuple, with current state and updater function.
66+
67+
## [1.0.1] - 2025-04-19
68+
69+
#### Added
70+
71+
- README.md added along with basic usage for primitives.
72+
- 🟡 **TODO**: README requires detailed examples, along with usage of complex data types.
73+
74+
## [1.0.0] - 2025-01-22
75+
76+
#### Added
77+
78+
- Initial release with core pub-sub and minimal TypeScript types.

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
## react-orba
1+
# react-orba
22

3-
A simple, lightweight state management library for React applications. Works
4-
over the pub-sub model and batches state updates.
3+
A simple, lightweight state management library for React applications. Works over the pub-sub model
4+
and batches state updates.
55

6-
### Usage
6+
## Usage
77

88
1. At the root of your app or a module, create a store.
99

@@ -29,8 +29,7 @@ const initialParams: AppState = {
2929
const appStore = createStore<AppState>(initialParams);
3030
```
3131

32-
2. In your component, slice out your state variable(s) using the `useBeacon`
33-
hook.
32+
2. In your component, slice out your state variable(s) using the `useBeacon` hook.
3433

3534
```tsx
3635
import { appStore } from '../App';
@@ -44,7 +43,7 @@ const [theme, setTheme] = useBeacon(appStore, (state) => state.theme);
4443
<button onClick={() => setTheme('light')}>Toggle theme</button>
4544
```
4645

47-
### Running on local
46+
## Running on local
4847

4948
1. Clone the repo
5049

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-orba",
3-
"version": "1.0.6",
3+
"version": "1.0.7",
44
"description": "A global state manager for React applications, minus the ceremony",
55
"main": "dist/index.js",
66
"module": "dist/index.esm.js",

src/core/store.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { unstable_batchedUpdates as batch } from 'react-dom';
2-
import { Listener } from '../types';
2+
import { Listener, StoreSetOptions, StoreSubscriptionOptions } from '../types';
33

44
function createStore<T>(initialState: T) {
55
let state: T = initialState;
@@ -9,19 +9,22 @@ function createStore<T>(initialState: T) {
99
return state;
1010
}
1111

12-
function set(next: T | ((prev: T) => T)) {
12+
function set(next: T | ((prev: T) => T), options?: StoreSetOptions) {
1313
const prev: T = state;
1414
state = typeof next === 'function' ? (next as any)(prev) : next;
1515

16-
if (Object.is(prev, state)) return;
16+
if (Object.is(prev, state) || options?.silent) return;
1717

1818
batch(() => {
1919
listeners.forEach((listener) => listener(state, prev));
2020
});
2121
}
2222

23-
function subscribe(listener: Listener<T>) {
23+
function subscribe(listener: Listener<T>, options?: StoreSubscriptionOptions) {
2424
listeners.add(listener);
25+
if (options?.fireImmediately) {
26+
listener(state, state);
27+
}
2528
return () => listeners.delete(listener);
2629
}
2730

src/core/useBeacon.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,19 @@ import { useSyncExternalStore, useCallback } from 'react';
22
import { Store } from '../types';
33
import { unstable_batchedUpdates } from 'react-dom';
44

5-
function useBeacon<T, U>(
5+
function useValue<T, U>(
66
store: Store<T>,
77
selector: (state: T) => U,
88
): [U, (updater: (previousState: U) => U) => void] {
9-
const getSnapshot = useCallback(
10-
() => selector(store.get()),
11-
[store, selector],
12-
);
13-
const state: U = useSyncExternalStore(
14-
store.subscribe,
15-
getSnapshot,
16-
getSnapshot,
17-
);
9+
const getSnapshot = useCallback(() => selector(store.get()), [store, selector]);
10+
const state: U = useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
1811

1912
const setState = useCallback(
2013
(updater: (prevState: U) => U) => {
2114
unstable_batchedUpdates(() => {
2215
store.set((prevState) => {
2316
const selectedState = selector(prevState);
24-
const newState =
25-
typeof updater === 'function' ? updater(selectedState) : updater;
17+
const newState = typeof updater === 'function' ? updater(selectedState) : updater;
2618
return {
2719
...prevState,
2820
...mapSelectedStateToGlobalState(selector, newState, prevState),
@@ -48,23 +40,19 @@ function mapSelectedStateToGlobalState<T, U>(
4840
return { [key]: newState } as Partial<T>;
4941
}
5042
}
51-
throw new Error(
52-
'Could not map primitive selected state back to global state.',
53-
);
43+
throw new Error('Could not map primitive selected state back to global state.');
5444
}
5545

5646
const selectedKeys = Object.keys(selector(prevState) as Object);
5747
const updates: Partial<T> = {};
5848

5949
selectedKeys.forEach((key) => {
6050
if (key in (newState as Object)) {
61-
updates[key as keyof T] = newState[
62-
key as keyof U
63-
] as unknown as T[keyof T];
51+
updates[key as keyof T] = newState[key as keyof U] as unknown as T[keyof T];
6452
}
6553
});
6654

6755
return updates;
6856
}
6957

70-
export default useBeacon;
58+
export default useValue;

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import useBeacon from './core/useBeacon';
1+
import useValue from './core/useBeacon';
22
import createStore from './core/store';
33

4-
export { createStore, useBeacon };
4+
export { createStore, useValue };

src/types.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
type Listener<T> = (state: T, prev: T) => void;
22
type EqualityFn<T> = (first: T, second: T) => boolean;
33

4+
type StoreSetOptions = {
5+
silent?: boolean;
6+
};
7+
8+
type StoreSubscriptionOptions = {
9+
fireImmediately?: boolean;
10+
};
11+
412
type Store<T> = {
513
get: () => T;
6-
set: (next: T | ((prev: T) => T), opts?: { silent?: boolean }) => void;
7-
subscribe: (
8-
listener: Listener<T>,
9-
options?: { fireImmediately?: boolean },
10-
) => () => void;
14+
set: (next: T | ((prev: T) => T), options?: StoreSetOptions) => void;
15+
subscribe: (listener: Listener<T>, options?: StoreSubscriptionOptions) => () => void;
1116
};
1217

13-
export { Listener, EqualityFn, Store };
18+
export { EqualityFn, Listener, Store, StoreSetOptions, StoreSubscriptionOptions };

0 commit comments

Comments
 (0)