Skip to content

Commit d04adfb

Browse files
committed
[ADD] useEventDispatcher hook
1 parent 2c82483 commit d04adfb

10 files changed

Lines changed: 134 additions & 5 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { BaseSyntheticEvent, useCallback, useRef, useState } from "react"
2+
import { useEventDispatcher, useEventListener } from "../../../../../../packages/react-tools/src/hooks"
3+
4+
/**
5+
The component has:
6+
- A state variable.
7+
- An _inputRef_ ref variable attacched to an input element contained in a form.
8+
- An dispatch function returned from _useEventDispatcher_ with _inputRef_ as element.
9+
- An onSubmit function to handle form onSubmit that invokes _dispatch_ function with a CustomEvent("demo") which detail is valued with input value taken from onSubmit event.
10+
- A useEventListener of type _demo_, on element _inputRef_ and with a listener that takes CustomEvent and invokes setState with event detail.
11+
*/
12+
export const UseEventDispatcher = () => {
13+
const [state, setState] = useState("");
14+
const inputRef = useRef<HTMLInputElement>(null);
15+
const dispatch = useEventDispatcher(inputRef);
16+
17+
const onSubmit = useCallback((e: BaseSyntheticEvent) => {
18+
dispatch(new CustomEvent("demo", { detail: e.target[0].value }));
19+
e.preventDefault();
20+
}, [dispatch]);
21+
22+
useEventListener({
23+
type: "demo",
24+
element: inputRef,
25+
listener: (evt) => {
26+
setState((evt as CustomEvent<string>).detail);
27+
},
28+
});
29+
30+
return (<>
31+
<em>State is: </em> {state}
32+
<br />
33+
<form noValidate onSubmit={onSubmit}>
34+
<input type="text" ref={inputRef}/>
35+
</form>
36+
</>)
37+
38+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const COMPONENTS = [
1818
"useLayoutEffectCompare",
1919
"useLayoutEffectDeepCompare",
2020
"usePubSubModel",
21+
"useEventDispatcher",
2122
"useEventListener"
2223
],
2324
[
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# useEventDispatcher
2+
Hook to dispatch an Event or a CustomEvent
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseEventDispatcher = () => {
8+
const [state, setState] = useState("");
9+
const inputRef = useRef<HTMLInputElement>(null);
10+
const dispatch = useEventDispatcher(inputRef);
11+
12+
const onSubmit = useCallback((e: BaseSyntheticEvent) => {
13+
dispatch(new CustomEvent("demo", { detail: e.target[0].value }));
14+
e.preventDefault();
15+
}, [dispatch]);
16+
17+
useEventListener({
18+
type: "demo",
19+
element: inputRef,
20+
listener: (evt) => {
21+
setState((evt as CustomEvent<string>).detail);
22+
},
23+
});
24+
25+
return (<>
26+
state is {state}
27+
<br />
28+
<form noValidate onSubmit={onSubmit}>
29+
<input type="text" ref={inputRef}/>
30+
</form>
31+
</>)
32+
33+
}
34+
```
35+
36+
> The component has:
37+
> - A state variable.
38+
> - An _inputRef_ ref variable attacched to an input element contained in a form.
39+
> - An dispatch function returned from _useEventDispatcher_ with _inputRef_ as element.
40+
> - An onSubmit function to handle form onSubmit that invokes _dispatch_ function with a CustomEvent("demo") which detail is valued with input value taken from onSubmit event.
41+
> - A useEventListener of type _demo_, on element _inputRef_ and a listener that takes CustomEvent and invokes setState with event detail.
42+
43+
44+
## API
45+
46+
```tsx
47+
useEventDispatcher (element: RefObject<HTMLElement> | Window = window): (evt: Event | CustomEvent) => void
48+
```
49+
50+
> ### Params
51+
>
52+
> - __element=window?__: _RefObject<HTMLElement> | Window_
53+
target on which dispatch event
54+
>
55+
56+
> ### Returns
57+
>
58+
> __dispatch__: function that dispatch the event on target
59+
> - _(evt: Event | CustomEvent) => void_
60+
>

apps/react-tools-demo/src/markdown/useEventListener.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ export { UseEventListener };
2525
```
2626

2727
> The component has:
28-
> - A _buttonRef_ ref variable linked to an button node element.
28+
> - A _buttonRef_ ref variable linked to an button node element with text click to log.
2929
> - A _useEventListener_ invocation with these options: _type_="click", _listener_=(e:Event)=>console.log(e). So the eventListener is attached to window and the invocation result is used to initialize a variable remove that remove the eventListener.
3030
> - A _useEventListener_ invocation like that above but attached at button node by _buttonRef_ with _listenerOpts_ capture=_true_ and that stops event propagation.
31-
> - A button that, if clicked, invokes the removeEventListener
31+
> - A _button_ with text Remove that, if clicked, invokes the removeEventListener.
32+
>
3233
> At every click on Demo area the eventListener attached on object window logs in console the event, while the eventListener attached on button logs when button is clicked. When button remove is clicked it removes the eventListener on window object. The button eventListener is removed during component unmount.
3334
3435

apps/react-tools-demo/src/router/Router.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import isMouseEventMD from '../markdown/isMouseEvent.md?url';
2626
import isTouchEventMD from '../markdown/isTouchEvent.md?url';
2727
import isClientMD from '../markdown/isClient.md?url';
2828
import usePubSubModelMD from '../markdown/usePubSubModel.md?url';
29+
import useEventDispatcherMD from '../markdown/useEventDispatcher.md?url';
2930
import { UsePrevious } from '../components/hooks/usePrevious/UsePrevious';
3031
import { UseStateHistory } from '../components/hooks/useStateHistory/UseStateHistory';
3132
import { UseCallbackCompare } from '../components/hooks/useCallbackCompare/UseCallbackCompare';
@@ -39,6 +40,7 @@ import { UsePubSubModel } from '../components/hooks/usePubSubModel/UsePubSubMode
3940
import { UseMemoizedFunction } from '../components/hooks/useMemoizedFunction/UseMemoizedFunction';
4041
import { UseLocalStorage } from '../components/hooks/useLocalStorage/UseLocalStorage';
4142
import { UseEventListener } from '../components/hooks/useEventListener/UseEventListener';
43+
import { UseEventDispatcher } from '../components/hooks/useEventDispatcher/UseEventDispatcher';
4244

4345
function Router() {
4446
const router = createBrowserRouter([
@@ -167,6 +169,13 @@ function Router() {
167169
markdown={usePubSubModelMD}
168170
/>
169171
},
172+
{
173+
path: "/useEventDispatcher",
174+
element: <ComponentLayout
175+
component={<UseEventDispatcher />}
176+
markdown={useEventDispatcherMD}
177+
/>
178+
},
170179
{
171180
path: "/useEventListener",
172181
element: <ComponentLayout

packages/react-tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
- [ ] useFullscreen (check browser compatibility)
5252
- [ ] useInViewport (with area ratio of element: check if is equal to useIntersectionObserver)
5353
- [ ] useLongPress
54-
- [ ] useEventDispatcher
54+
- [x] useEventDispatcher
5555
- [x] usePubSubModel
5656

5757
- __DOM API__

packages/react-tools/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export { useEffectDeepCompare } from "./useEffectDeepCompare";
1414
export { useLayoutEffectCompare } from "./useLayoutEffectCompare";
1515
export { useLayoutEffectDeepCompare } from "./useLayoutEffectDeepCompare";
1616
export { usePubSubModel } from './usePubSubModel';
17+
export { useEventDispatcher } from './useEventDispatcher';
1718
export { useEventListener } from './useEventListener';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { RefObject } from "react"
2+
import { useMemoizedFunction } from ".";
3+
4+
/**
5+
* __`useEventDispatcher`__: Hook to dispatch an Event or a CustomEvent
6+
* @param {RefObject<HTMLElement> | Window} [element=window] - target on which dispatch event
7+
* @returns {(evt: Event | CustomEvent) => void} dispatch - function that dispatch the event on target
8+
*/
9+
export const useEventDispatcher = (element: RefObject<HTMLElement> | Window = window): (evt: Event | CustomEvent) => void => {
10+
const dispatch = useMemoizedFunction((evt: Event | CustomEvent) => {
11+
const target = Reflect.has(element, "current")
12+
? (element as RefObject<HTMLElement>).current
13+
: element;
14+
(target as HTMLElement | Window).dispatchEvent(evt);
15+
})
16+
17+
return dispatch;
18+
}

packages/react-tools/src/hooks/useMemoizedFunction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useRef } from "react";
1+
import { useCallback, useLayoutEffect, useRef } from "react";
22

33
/**
44
* **`useMemoizedFunction`**: Hook to store a function that will never change while keeping its dependencies always up to date. Can be used instead of _useCallback_, without esplicity dependencies array.
@@ -10,7 +10,7 @@ export const useMemoizedFunction = <T extends (...args: any[]) => any>(fn: T) =>
1010

1111
const memoizedFn = useCallback((...args: Parameters<T>):ReturnType<T> => fnRef.current(...args), []);
1212

13-
useEffect(() => {
13+
useLayoutEffect(() => {
1414
fnRef.current = fn
1515
});
1616

packages/react-tools/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export {
1919
useLayoutEffectCompare,
2020
useLayoutEffectDeepCompare,
2121
usePubSubModel,
22+
useEventDispatcher,
2223
useEventListener
2324
} from './hooks'
2425

0 commit comments

Comments
 (0)