Skip to content

Commit 40adcaa

Browse files
authored
Feat/use custom event (#235)
* Addition of use custom event hook * feat: add docs for the useCustomEvent hook * chore: add the changelog for useCustomEvent
1 parent 552d3c3 commit 40adcaa

File tree

8 files changed

+215
-6
lines changed

8 files changed

+215
-6
lines changed

.changeset/calm-clouds-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@abhushanaj/react-hooks': minor
3+
---
4+
5+
Addition of the useCustomEvent() hook
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { act, renderHook } from '@testing-library/react';
2+
import { describe, expect, it, vi } from 'vitest';
3+
4+
import { useCustomEvent } from '.';
5+
6+
describe('useCustomEvent() hook', () => {
7+
it('should be defined', () => {
8+
expect.hasAssertions();
9+
expect(useCustomEvent).toBeDefined();
10+
});
11+
12+
/**
13+
* Since the hook is composed of two other hooks, we simply check for integrity here.
14+
*/
15+
it('should dispatch, subscribe and unsubscribe', () => {
16+
expect.hasAssertions();
17+
18+
const cb = vi.fn();
19+
const customEventName = 'customEvt';
20+
21+
const { result } = renderHook(({ callback, eventName }) => useCustomEvent(eventName, callback), {
22+
initialProps: {
23+
eventName: customEventName,
24+
callback: cb
25+
}
26+
});
27+
28+
act(() => {
29+
result.current[0]();
30+
});
31+
32+
expect(cb).toHaveBeenCalledTimes(1);
33+
34+
act(() => {
35+
result.current[1]();
36+
result.current[0]();
37+
});
38+
39+
expect(cb).toHaveBeenCalledTimes(1);
40+
});
41+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useDispatchCustomEvent } from '../useDispatchCustomEvent';
2+
import { useSubscribeToCustomEvent } from '../useSubscribeToCustomEvent';
3+
4+
/**
5+
* useCustomEvent() - Custom react hook to manage the lifecycle of custom events
6+
* @see - https://react-hooks.abhushan.dev/hooks/state/usecustomevent/
7+
*/
8+
export const useCustomEvent = <T>(eventName: string, callback: (e: CustomEvent<T>) => void) => {
9+
const dispatch = useDispatchCustomEvent(eventName);
10+
11+
const unSub = useSubscribeToCustomEvent(eventName, callback);
12+
13+
return [dispatch, unSub] as const;
14+
};

react-hooks/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ export { useDebounce } from './hooks/useDebounce';
6161
export { useThrottle } from './hooks/useThrottle';
6262
export { useDispatchCustomEvent } from './hooks/useDispatchCustomEvent';
6363
export { useSubscribeToCustomEvent } from './hooks/useSubscribeToCustomEvent';
64+
export { useCustomEvent } from './hooks/useCustomEvent';
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import { useCustomEvent } from '@abhushanaj/react-hooks';
3+
4+
import Button from '@/components/docs/button';
5+
6+
type CustomWindow = Window &
7+
typeof globalThis & {
8+
state: {
9+
value: number;
10+
};
11+
};
12+
13+
function UseCustomEventExample() {
14+
const [count, setCount] = React.useState(0);
15+
16+
const [dispatch, unSub] = useCustomEvent<{ newValue: number }>('valueChanged', (e) => {
17+
setCount(e.detail.newValue);
18+
});
19+
20+
React.useEffect(() => {
21+
(window as CustomWindow).state = { value: 10 };
22+
dispatch({ newValue: 0 });
23+
24+
(window as CustomWindow).state = new Proxy((window as CustomWindow).state, {
25+
set(obj, prop, value) {
26+
if (prop === 'value') {
27+
dispatch({ newValue: value });
28+
}
29+
30+
// @ts-expect-error
31+
obj[prop] = value;
32+
33+
return true;
34+
}
35+
});
36+
}, []);
37+
38+
const updateWindowValue = (newValue: number) => {
39+
(window as CustomWindow).state.value = newValue;
40+
};
41+
42+
return (
43+
<div className="flex flex-col gap-2">
44+
<p>window.state.value is :{JSON.stringify(count)}</p>
45+
46+
<Button variant="secondary" onClick={() => updateWindowValue(20)}>
47+
Set window.state.value=20
48+
</Button>
49+
<Button variant="secondary" onClick={() => updateWindowValue(30)}>
50+
Set window.state.value=30
51+
</Button>
52+
53+
<Button variant="secondary" onClick={unSub}>
54+
Unsubscribe
55+
</Button>
56+
57+
<small>Update the window.state.value from console and see it reflect above.</small>
58+
59+
<small>
60+
The example above is simply creating a proxy over window.state and for every set action performed on it,
61+
dispatching action which the component is subscribing to.
62+
</small>
63+
</div>
64+
);
65+
}
66+
67+
export default UseCustomEventExample;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: useCustomEvent
3+
description: 'Manage the entire lifecycle for a custom event.'
4+
subtitle: 'Manage the entire lifecycle for a custom event.'
5+
sidebar:
6+
badge: 'New'
7+
---
8+
9+
import Example from '@/components/demo/useCustomEvent';
10+
import { DemoWrapper } from '@/components/demo/wrapper';
11+
12+
The `useCustomEvent` hook is helpful when you want manage the entire lifecycle for a custom event. From dispatch, to subscribing and cleanup, it's a one off solution.
13+
14+
This is helpful in scenarios like flushing pending actions after auth is completed, clearing pending analytics queue based on user action or sucessful load of analytics scripts.
15+
16+
The hook is composed using the [useDispatchCustomEvent](/hooks/utilities/usedispatchcustomevent/) and [useSubscribeToCustomEvent](/hooks/utilities/usesubscribetocustomevent/) hooks.
17+
18+
<DemoWrapper title="useCustomEvent">
19+
<Example client:load />
20+
</DemoWrapper>
21+
22+
## Usage
23+
24+
Import the hook from `@abhushanaj/react-hooks` and use in required component.
25+
26+
```tsx title="./src/App.tsx" ins={2,7-9} mark="useCustomEvent"
27+
import React from 'react';
28+
import { useCustomEvent } from '@abhushanaj/react-hooks';
29+
30+
function App() {
31+
const [value, setValue] = React.useState(0);
32+
33+
const [dispatch, unSub] = useCustomEvent<{ newValue: number }>('valueChanged', (e) => {
34+
setCount(e.detail.newValue);
35+
});
36+
37+
return (
38+
<div>
39+
<p>Value is: {value}</p>
40+
<button onClick={unSubscribe}>Unsubscribe</button>
41+
<button
42+
onClick={() => {
43+
dispatch({ newValue: value++ });
44+
}}
45+
>
46+
Unsubscribe
47+
</button>
48+
</div>
49+
);
50+
}
51+
52+
export default App;
53+
```
54+
55+
## Properties
56+
57+
1. The `useCustomEvent` automatically unsubscribes from the event on component unmount, so you do not need to worry about cleanups. However it does return a `unsubscribe` function back for cases where you want to take control of this. Once unsubscribe the event cannot be subscribed to again.
58+
59+
2. You can use the generic `useCustomEvent<Payload>(eventName, callback)` when you know the payload type. However, I recommend validating the payload first instead of relying on this approach.
60+
61+
## API Reference
62+
63+
### Parameters
64+
65+
| Parameter | Type | Description | Default |
66+
| ------------- | --------------------------- | ------------------------------------------------------- | ------- |
67+
| eventName | `string` | The name of the custom event name you want to dispatch. | N/A |
68+
| eventCallback | `(e: CustomEvent<T>=>void)` | The callback which gets invoked when event is fired. | N/A |
69+
70+
### Return Value
71+
72+
The return value follows the `[dispatch, unsubscribe]` tuple.
73+
74+
| Parameter | Type | Description | Default |
75+
| ----------- | -------------------- | ---------------------------------------------------------------------- | ------- |
76+
| dispatch | `(payload? T)=>void` | The dispatcher for the custom event which takes a payload as argument. | N/A |
77+
| unsubscribe | `()=>void` | The unsubscribe method for the event. | N/A |

www/src/content/docs/hooks/utilities/useDispatchCustomEvent.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import { DemoWrapper } from '@/components/demo/wrapper';
1111

1212
The `useDispatchCustomEvent` hook is helpful when you want to dispatch your own custom events with payloads that can be subscribed by any component in tree.
1313

14-
This is helpful in scenarios like flushing pending actions after auth is completed, clearing pending analytics queue based om user action or sucessful load of analytics scripts.
14+
This is helpful in scenarios like flushing pending actions after auth is completed, clearing pending analytics queue based on user action or sucessful load of analytics scripts.
1515

16-
The hook can be used in conjunction with[useSubscribeToCustomEvent](/hooks/utilities/usesubscribetocustomevent/).
16+
The hook can be used in conjunction with [useSubscribeToCustomEvent](/hooks/utilities/usesubscribetocustomevent/).
17+
18+
For cases when you want to manage the entire lifecycle from one hook you can simply use [useCustomEvent](/hooks/utilities/usecustomevent/), with uses both `useDispatchCustomEvent` and `useSubscribeToCustomEvent` hooks internally.
1719

1820
<DemoWrapper title="useDispatchCustomEvent">
1921
<Example client:load />

www/src/content/docs/hooks/utilities/useSubscribeToCustomEvent.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import { DemoWrapper } from '@/components/demo/wrapper';
1111

1212
The `useSubscribeToCustomEvent` hook is helpful when you want to subscribe and manage the lifecycle of your own custom events with payloads.
1313

14-
This is helpful in scenarios like flushing pending actions after auth is completed, clearing pending analytics queue based om user action or sucessful load of analytics scripts.
14+
This is helpful in scenarios like flushing pending actions after auth is completed, clearing pending analytics queue based on user action or sucessful load of analytics scripts.
1515

16-
The hook can be used in conjunction with[useDispatchCustomEvent](/hooks/utilities/usedispatchcustomevent/).
16+
The hook can be used in conjunction with [useDispatchCustomEvent](/hooks/utilities/usedispatchcustomevent/).
17+
18+
For cases when you want to manage the entire lifecycle from one hook you can simply use [useCustomEvent](/hooks/utilities/usecustomevent/), with uses both `useDispatchCustomEvent` and `useSubscribeToCustomEvent` hooks internally.
1719

1820
<DemoWrapper title="useSubscribeToCustomEvent">
1921
<Example client:load />
@@ -23,7 +25,7 @@ The hook can be used in conjunction with[useDispatchCustomEvent](/hooks/utilitie
2325

2426
Import the hook from `@abhushanaj/react-hooks` and use in required component.
2527

26-
```tsx title="./src/App.tsx" ins={2,10-13} mark="useSubscribeToCustomEvent"
28+
```tsx title="./src/App.tsx" ins={2,10-12} mark="useSubscribeToCustomEvent"
2729
import React from 'react';
2830
import { useSubscribeToCustomEvent } from '@abhushanaj/react-hooks';
2931

@@ -49,7 +51,7 @@ export default App;
4951

5052
## Properties
5153

52-
1. The `useSubscribeToCustomEvent` automatically unsubscribes from the event on component mount, so you do not need to worry about cleanups. However it does return a `unsubscribe` function back for cases where you want to take control of this. Once unsubscribe the event cannot be subscribed to again.
54+
1. The `useSubscribeToCustomEvent` automatically unsubscribes from the event on component unmount, so you do not need to worry about cleanups. However it does return a `unsubscribe` function back for cases where you want to take control of this. Once unsubscribe the event cannot be subscribed to again.
5355

5456
2. You can use the generic `useSubscribeToCustomEvent<Payload>(eventName, callback)` when you know the payload type. However, I recommend validating the payload first instead of relying on this approach.
5557

0 commit comments

Comments
 (0)