react-usectx 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/README.md +2 -0
- package/babel.config.js +6 -0
- package/intex.ts +3 -0
- package/package.json +21 -0
- package/src/context.ts +37 -0
- package/src/create-context.test.ts +61 -0
- package/src/create-context.ts +39 -0
package/README.md
ADDED
package/babel.config.js
ADDED
package/intex.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-usectx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "jest"
|
|
7
|
+
},
|
|
8
|
+
"author": "https://jsf7.dev",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"description": "Custom React globally accessible context hook",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^19.1.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@babel/preset-env": "^7.28.0",
|
|
16
|
+
"@babel/preset-typescript": "^7.27.1",
|
|
17
|
+
"@types/react": "^19.1.8",
|
|
18
|
+
"jest": "^30.0.4",
|
|
19
|
+
"typescript": "^5.8.3"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useSyncExternalStore } from 'react'
|
|
4
|
+
import { CreateContext } from './create-context'
|
|
5
|
+
|
|
6
|
+
type cache = { [key: string]: InstanceType<typeof CreateContext> }
|
|
7
|
+
|
|
8
|
+
const cache: cache = {}
|
|
9
|
+
|
|
10
|
+
const getInstance = (name: string): InstanceType<typeof CreateContext> => {
|
|
11
|
+
if (!cache[name]) {
|
|
12
|
+
cache[name] = new CreateContext()
|
|
13
|
+
}
|
|
14
|
+
return cache[name]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function updateCtx(name: string): Function {
|
|
18
|
+
const ctx = getInstance(name)
|
|
19
|
+
return ctx.commit.bind(ctx)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getCtx<Snapshot>(name: string): Snapshot {
|
|
23
|
+
const context = getInstance(name)
|
|
24
|
+
const subscription = (callback: Function) => {
|
|
25
|
+
context.subscribe(callback)
|
|
26
|
+
return () => context.unsubscribe(callback)
|
|
27
|
+
}
|
|
28
|
+
return useSyncExternalStore(
|
|
29
|
+
subscription,
|
|
30
|
+
context.getState.bind(context),
|
|
31
|
+
context.getState.bind(context)
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useCtx(name: string): [ReactNode, Function] {
|
|
36
|
+
return [getCtx(name), updateCtx(name)]
|
|
37
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CreateContext } from "./create-context";
|
|
2
|
+
|
|
3
|
+
describe("CreateContext", () => {
|
|
4
|
+
let context: CreateContext;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
context = new CreateContext();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("should set and get state correctly", () => {
|
|
11
|
+
context.setState("hello");
|
|
12
|
+
expect(context.getState()).toBe("hello");
|
|
13
|
+
|
|
14
|
+
context.setState(123);
|
|
15
|
+
expect(context.getState()).toBe(123);
|
|
16
|
+
|
|
17
|
+
context.setState({ a: 1 });
|
|
18
|
+
expect(context.getState()).toEqual({ a: 1 });
|
|
19
|
+
|
|
20
|
+
context.setState(null);
|
|
21
|
+
expect(context.getState()).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("should subscribe and notify event handlers on commit", () => {
|
|
25
|
+
const handler1 = jest.fn();
|
|
26
|
+
const handler2 = jest.fn();
|
|
27
|
+
|
|
28
|
+
context.subscribe(handler1);
|
|
29
|
+
context.subscribe(handler2);
|
|
30
|
+
|
|
31
|
+
context.commit("new state");
|
|
32
|
+
|
|
33
|
+
expect(handler1).toHaveBeenCalledWith("new state");
|
|
34
|
+
expect(handler2).toHaveBeenCalledWith("new state");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("should not notify unsubscribed handlers", () => {
|
|
38
|
+
const handler = jest.fn();
|
|
39
|
+
|
|
40
|
+
context.subscribe(handler);
|
|
41
|
+
context.unsubscribe(handler);
|
|
42
|
+
context.commit("test");
|
|
43
|
+
|
|
44
|
+
expect(handler).not.toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("unsubscribe should not fail for non-existent handler", () => {
|
|
48
|
+
const handler = jest.fn();
|
|
49
|
+
expect(() => context.unsubscribe(handler)).not.toThrow();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("commit should update state and call handlers", () => {
|
|
53
|
+
const handler = jest.fn();
|
|
54
|
+
context.subscribe(handler);
|
|
55
|
+
|
|
56
|
+
context.commit({ key: "value" });
|
|
57
|
+
|
|
58
|
+
expect(context.getState()).toEqual({ key: "value" });
|
|
59
|
+
expect(handler).toHaveBeenCalledWith({ key: "value" });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type eventHandlers = Array<Function>;
|
|
2
|
+
|
|
3
|
+
type state =
|
|
4
|
+
| { [key: string]: any }
|
|
5
|
+
| Array<any>
|
|
6
|
+
| string
|
|
7
|
+
| number
|
|
8
|
+
| boolean
|
|
9
|
+
| null;
|
|
10
|
+
|
|
11
|
+
export class CreateContext {
|
|
12
|
+
#eventHandlers: eventHandlers = [];
|
|
13
|
+
|
|
14
|
+
#state: state = null;
|
|
15
|
+
|
|
16
|
+
subscribe(handler: Function): void {
|
|
17
|
+
this.#eventHandlers.push(handler);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
unsubscribe(handler: Function): void {
|
|
21
|
+
const index = this.#eventHandlers.indexOf(handler);
|
|
22
|
+
if (index !== -1) {
|
|
23
|
+
this.#eventHandlers.splice(index, 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
commit(state: state): void {
|
|
28
|
+
this.setState(state);
|
|
29
|
+
this.#eventHandlers.forEach((callback) => callback.call(undefined, state));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setState(state: state): void {
|
|
33
|
+
this.#state = state;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getState(): state {
|
|
37
|
+
return this.#state;
|
|
38
|
+
}
|
|
39
|
+
}
|