Skip to content

Commit 07e81a0

Browse files
authored
Improve TypeScript types for custom events. (#29)
* Improve TypeScript types for custom events. * Improve ResolvedEvent for custom events. * Simplify ResolvedEvent and rename to EventSourceEvent, add beter typing for removeAllEventListeners and dispatch. * Update docs.
1 parent a16e01c commit 07e81a0

File tree

2 files changed

+74
-18
lines changed

2 files changed

+74
-18
lines changed

README.md

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,19 @@ If you use Redux you can get the actual value directly from the store instance.
139139

140140
```typescript
141141
// full example: https://snack.expo.dev/@quiknull/react-native-sse-redux-example
142+
type CustomEvents = "ping";
143+
142144
const Example: React.FC = () => {
143-
const userName = useSelector((state: RootState) => state.user.name);
145+
const name = useSelector((state: RootState) => state.user.name);
144146

145-
const pingHandler: EventSourceListener = useCallback(
147+
const pingHandler: EventSourceListener<CustomEvents, 'ping'> = useCallback(
146148
(event) => {
147149
// In Event Source Listeners in connection with redux
148150
// you should read state directly from store object.
149-
console.log("User name from component selector: ", userName); // bad
151+
console.log("User name from component selector: ", name); // bad
150152
console.log("User name directly from store: ", store.getState().user.name); // good
151153
},
152-
[userName]
154+
[]
153155
);
154156

155157
useEffect(() => {
@@ -160,7 +162,7 @@ const Example: React.FC = () => {
160162
"https://example.com/my-private-topic"
161163
);
162164

163-
const es = new EventSource(url, {
165+
const es = new EventSource<CustomEvents>(url, {
164166
headers: {
165167
Authorization: {
166168
toString: function () {
@@ -201,7 +203,7 @@ const options: EventSourceOptions = {
201203
Using EventSource you can handle custom events invoked by the server:
202204

203205
```typescript
204-
import EventSource from "react-native-sse";
206+
import EventSource, { EventSourceListener, EventSourceEvent } from "react-native-sse";
205207

206208
type MyCustomEvents = "ping" | "clientConnected" | "clientDisconnected";
207209

@@ -226,6 +228,57 @@ es.addEventListener("clientDisconnected", (event) => {
226228
});
227229
```
228230

231+
Using one listener for all events:
232+
233+
```typescript
234+
import EventSource, { EventSourceListener } from "react-native-sse";
235+
236+
type MyCustomEvents = "ping" | "clientConnected" | "clientDisconnected";
237+
238+
const es = new EventSource<MyCustomEvents>(
239+
"https://your-sse-server.com/.well-known/hub"
240+
);
241+
242+
const listener: EventSourceListener<MyCustomEvents> = (event) => {
243+
if (event.type === 'open') {
244+
// connection opened
245+
} else if (event.type === 'message') {
246+
// ...
247+
} else if (event.type === 'ping') {
248+
// ...
249+
}
250+
}
251+
es.addEventListener('open', listener);
252+
es.addEventListener('message', listener);
253+
es.addEventListener('ping', listener);
254+
```
255+
256+
Using generic type for one event:
257+
258+
```typescript
259+
import EventSource, { EventSourceListener, EventSourceEvent } from "react-native-sse";
260+
261+
type MyCustomEvents = "ping" | "clientConnected" | "clientDisconnected";
262+
263+
const es = new EventSource<MyCustomEvents>(
264+
"https://your-sse-server.com/.well-known/hub"
265+
);
266+
267+
const pingListener: EventSourceListener<MyCustomEvents, 'ping'> = (event) => {
268+
// ...
269+
}
270+
// or
271+
const pingListener = (event: EventSourceEvent<'ping', MyCustomEvents>) => {
272+
// ...
273+
}
274+
275+
es.addEventListener('ping', pingListener);
276+
```
277+
278+
`MyCustomEvents` in `EventSourceEvent` is optional, but it's recommended to use it in order to have better type checking.
279+
280+
---
281+
229282
Custom events always emit result with following interface:
230283

231284
```typescript

index.d.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
export type EventType = 'open' | 'message' | 'error' | 'close';
2-
3-
export interface BaseEvent {
4-
type: string;
5-
}
1+
export type BuiltInEventType = 'open' | 'message' | 'error' | 'close';
2+
export type EventType<E extends string = never> = E | BuiltInEventType;
63

74
export interface MessageEvent {
85
type: 'message';
@@ -54,20 +51,26 @@ export interface EventSourceOptions {
5451
pollingInterval?: number;
5552
}
5653

57-
export type EventSourceEvent = MessageEvent | OpenEvent | CloseEvent | TimeoutEvent | ErrorEvent | ExceptionEvent;
54+
type BuiltInEventMap = {
55+
'message': MessageEvent,
56+
'open': OpenEvent,
57+
'close': CloseEvent,
58+
'error': ErrorEvent | TimeoutEvent | ExceptionEvent,
59+
};
5860

59-
export type EventSourceListener<E extends string = never> = (
60-
event: CustomEvent<E> | EventSourceEvent
61+
export type EventSourceEvent<E extends T, T extends string = any> = E extends BuiltInEventType ? BuiltInEventMap[E] : CustomEvent<E>;
62+
export type EventSourceListener<E extends string = never, T extends EventType<E> = EventType<E>> = (
63+
event: EventSourceEvent<T>
6164
) => void;
6265

6366
declare class EventSource<E extends string = never> {
6467
constructor(url: URL | string, options?: EventSourceOptions);
6568
open(): void;
6669
close(): void;
67-
addEventListener(type: E | EventType, listener: EventSourceListener<E>): void;
68-
removeEventListener(type: E | EventType, listener: EventSourceListener<E>): void;
69-
removeAllEventListeners(type?: E | EventType): void;
70-
dispatch(type: E | EventType, data: E | EventSourceEvent): void;
70+
addEventListener<T extends EventType<E>>(type: T, listener: EventSourceListener<E, T>): void;
71+
removeEventListener<T extends EventType<E>>(type: T, listener: EventSourceListener<E, T>): void;
72+
removeAllEventListeners<T extends EventType<E>>(type?: T): void;
73+
dispatch<T extends EventType<E>>(type: T, data: EventSourceEvent<T>): void;
7174
}
7275

7376
export default EventSource;

0 commit comments

Comments
 (0)