Skip to content

Commit 763a758

Browse files
committed
main 🧊 add create event emmiter
1 parent c6a70d3 commit 763a758

File tree

9 files changed

+213
-5
lines changed

9 files changed

+213
-5
lines changed

‎packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@siberiacancode/reactuse",
3-
"version": "0.2.15",
3+
"version": "0.2.14",
44
"description": "The ultimate collection of react hooks",
55
"author": {
66
"name": "SIBERIA CAN CODE 🧊",
@@ -76,7 +76,7 @@
7676
"react": "^19.1.0",
7777
"react-dom": "^19.1.0",
7878
"shx": "^0.4.0",
79-
"vite": "^7.0.0",
79+
"vite": "^7.0.2",
8080
"vite-plugin-dts": "^4.5.4",
8181
"vitest": "^3.2.4"
8282
},
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
/**
3+
* @name createEventEmitter
4+
* @description - Creates a type-safe event emitter
5+
* @category Helpers
6+
*
7+
* @template Events - The type of events and their data
8+
* @returns {Events} - Object containing event emitter methods and hook
9+
*
10+
* @example
11+
* const { instance, push, subscribe, unsubscribe, useSubscribe } = createEventEmitter<{ foo: number }>();
12+
*/
13+
export const createEventEmitter = () => {
14+
const eventTarget = new EventTarget();
15+
const push = (event, data) => eventTarget.dispatchEvent(new CustomEvent(event, { detail: data }));
16+
const subscribe = (event, listener) => {
17+
const callback = (event) => listener(event.detail);
18+
eventTarget.addEventListener(event, callback);
19+
return () => eventTarget.removeEventListener(event, callback);
20+
};
21+
const unsubscribe = (event, listener) => {
22+
const callback = (event) => listener(event.detail);
23+
eventTarget.removeEventListener(event, callback);
24+
};
25+
const useSubscribe = (event, listener) => {
26+
const [data, setData] = useState(undefined);
27+
const listenerRef = useRef(listener);
28+
listenerRef.current = listener;
29+
useEffect(() => {
30+
const onSubscribe = (data) => {
31+
setData(data);
32+
listenerRef.current?.(data);
33+
};
34+
subscribe(event, onSubscribe);
35+
return () => {
36+
unsubscribe(event, onSubscribe);
37+
};
38+
}, [event]);
39+
return data;
40+
};
41+
return {
42+
instance: eventTarget,
43+
push,
44+
subscribe,
45+
unsubscribe,
46+
useSubscribe
47+
};
48+
};

‎packages/core/src/bundle/helpers/createStore/createStore.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { useSyncExternalStore } from 'react';
55
* @category Helpers
66
*
77
* @template Value - The type of the store state
8-
*
98
* @param {StateCreator<Value>} createState - Function that initializes the store state
109
* @returns {StoreApi<Value>} - Object containing store methods and hook for accessing state
1110
*
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './createContext/createContext';
2+
export * from './createEventEmitter/createEventEmitter';
23
export * from './createReactiveContext/createReactiveContext';
34
export * from './createStore/createStore';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createEventEmitter, useField } from '@siberiacancode/reactuse';
2+
3+
const DEFAULT_PROFILE = { name: 'John Doe', age: 30 };
4+
const profileEmitter = createEventEmitter<{
5+
'profile:update': { name: string; age: number };
6+
}>();
7+
8+
const NameFieldInfo = () => {
9+
const profileEventData = profileEmitter.useSubscribe('profile:update');
10+
11+
return (
12+
<div>
13+
<strong>Name:</strong> <span>{profileEventData?.name ?? DEFAULT_PROFILE.name}</span>
14+
</div>
15+
);
16+
};
17+
18+
const AgeFieldInfo = () => {
19+
const profileEventData = profileEmitter.useSubscribe('profile:update');
20+
21+
return (
22+
<div>
23+
<strong>Age:</strong> <span>{profileEventData?.age ?? DEFAULT_PROFILE.age}</span>
24+
</div>
25+
);
26+
};
27+
28+
const Demo = () => {
29+
const nameField = useField({
30+
initialValue: DEFAULT_PROFILE.name
31+
});
32+
33+
const ageField = useField({
34+
initialValue: DEFAULT_PROFILE.age
35+
});
36+
37+
return (
38+
<div className='rounded-lg p-4 shadow-md'>
39+
<div className='mb-6'>
40+
<h3 className='font-semibold'>Current Profile:</h3>
41+
<NameFieldInfo />
42+
<AgeFieldInfo />
43+
</div>
44+
45+
<div className='mb-4'>
46+
<div className='mb-2'>
47+
<strong className='font-semibold'>Name:</strong>
48+
<input
49+
type='text'
50+
{...nameField.register()}
51+
onChange={(event) =>
52+
profileEmitter.push('profile:update', {
53+
name: event.target.value,
54+
age: ageField.getValue() || DEFAULT_PROFILE.age
55+
})
56+
}
57+
/>
58+
{nameField.error && <span className='ml-2 text-sm text-red-500'>{nameField.error}</span>}
59+
</div>
60+
61+
<div className='mb-2'>
62+
<strong className='font-semibold'>Age:</strong>
63+
<input
64+
type='number'
65+
{...ageField.register()}
66+
onChange={(event) =>
67+
profileEmitter.push('profile:update', {
68+
name: nameField.getValue() || DEFAULT_PROFILE.name,
69+
age: +event.target.value
70+
})
71+
}
72+
/>
73+
{ageField.error && <span className='ml-2 text-sm text-red-500'>{ageField.error}</span>}
74+
</div>
75+
</div>
76+
77+
<div className='flex gap-2'>
78+
<button
79+
className='rounded bg-gray-500 px-4 py-2 text-white hover:bg-gray-600'
80+
onClick={() => {
81+
nameField.reset();
82+
ageField.reset();
83+
profileEmitter.push('profile:update', DEFAULT_PROFILE);
84+
}}
85+
>
86+
Reset Form
87+
</button>
88+
</div>
89+
</div>
90+
);
91+
};
92+
93+
export default Demo;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
/**
4+
* @name createEventEmitter
5+
* @description - Creates a type-safe event emitter
6+
* @category Helpers
7+
*
8+
* @template Events - The type of events and their data
9+
* @returns {Events} - Object containing event emitter methods and hook
10+
*
11+
* @example
12+
* const { instance, push, subscribe, unsubscribe, useSubscribe } = createEventEmitter<{ foo: number }>();
13+
*/
14+
export const createEventEmitter = <Events extends Record<string, any> = Record<string, any>>() => {
15+
const eventTarget = new EventTarget();
16+
17+
const push = <Event extends keyof Events>(event: Event, data: Events[Event]) =>
18+
eventTarget.dispatchEvent(new CustomEvent(event as string, { detail: data }));
19+
20+
const subscribe = <Key extends keyof Events>(
21+
event: Key,
22+
listener: (data: Events[Key]) => void
23+
) => {
24+
const callback = (event: Event) => listener((event as CustomEvent).detail);
25+
26+
eventTarget.addEventListener(event as string, callback);
27+
return () => eventTarget.removeEventListener(event as string, callback);
28+
};
29+
30+
const unsubscribe = <Key extends keyof Events>(
31+
event: Key,
32+
listener: (data: Events[Key]) => void
33+
) => {
34+
const callback = (event: Event) => listener((event as CustomEvent).detail);
35+
eventTarget.removeEventListener(event as string, callback);
36+
};
37+
38+
const useSubscribe = <Event extends keyof Events>(
39+
event: Event,
40+
listener?: (data: Events[Event]) => void
41+
) => {
42+
const [data, setData] = useState<Events[Event] | undefined>(undefined);
43+
const listenerRef = useRef(listener);
44+
listenerRef.current = listener;
45+
46+
useEffect(() => {
47+
const onSubscribe = (data: Events[Event]) => {
48+
setData(data);
49+
listenerRef.current?.(data);
50+
};
51+
subscribe(event, onSubscribe);
52+
return () => {
53+
unsubscribe(event, onSubscribe);
54+
};
55+
}, [event]);
56+
57+
return data;
58+
};
59+
60+
return {
61+
instance: eventTarget,
62+
push,
63+
subscribe,
64+
unsubscribe,
65+
useSubscribe
66+
};
67+
};

‎packages/core/src/helpers/createStore/createStore.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export interface StoreApi<Value> {
1919
* @category Helpers
2020
*
2121
* @template Value - The type of the store state
22-
*
2322
* @param {StateCreator<Value>} createState - Function that initializes the store state
2423
* @returns {StoreApi<Value>} - Object containing store methods and hook for accessing state
2524
*
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './createContext/createContext';
2+
export * from './createEventEmitter/createEventEmitter';
23
export * from './createReactiveContext/createReactiveContext';
34
export * from './createStore/createStore';

‎packages/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"@types/react-dom": "^19.1.6",
4343
"clsx": "^2.1.1",
4444
"comment-parser": "^1.4.1",
45-
"lucide-vue-next": "^0.523.0",
45+
"lucide-vue-next": "^0.525.0",
4646
"md5": "^2.3.0",
4747
"react": "^19.1.0",
4848
"react-dom": "^19.1.0",

0 commit comments

Comments
 (0)