Dispatching Events With React and TypeScript
This publication demonstrates a simple way to dispatch and listen to events with React and TypeScript. For simplicity, I will assume that you have already set up a React and TypeScript project of which you want event dispatching capabilities. This publication is also based on the rxjs library, which we will detail below.
Prerequisites
Install rxjs
RxJS is a library for composing asynchronous and event-based programs by using observable sequences.
rxjs is also maintained regularly, and is easy to use. You can view install instructions, as well as documentation, on their website here. Let’s begin by adding the rxjs and react-rxjs libraries to our project by running the following command from your project directory containing your package.json
file:
npm i rxjs @react-rxjs/core @react-rxjs/utils
Define an Event Enum
Firstly, let’s leverage TypeScript to create an enum called DomainEvents
, so that all of our possible events are defined in one area, like so:
// DomainEvents.ts
export enum DomainEvents {
ALL = 'ALL',
USER_CREATED = 'USER_CREATED'
}
Create an Event Dispatcher
Since dispatching events is a repeated process, let’s create a file called eventDispatcher.ts
and add the following code:
import { createSignal } from '@react-rxjs/utils';
import { merge } from 'rxjs';
import { v4 as uuid } from 'uuid'; // an arbitrary library for creating UUIDs
import { DomainEvents } from '@domain/constants/DomainEvents';
export type DomainEvent = {
id?: string;
name: DomainEvents;
data: {
[key: string]: unknown;
};
};
const [event$, setEvent] = createSignal<DomainEvent | undefined>();
export const eventMap$ = merge(event$);
const dispatch = (event: DomainEvent): void => {
if (!event.id) {
event.id = uuid();
}
setEvent(event);
setEvent(undefined);
};
export default Object.freeze({
dispatch
});
The code above that you just added to your eventDispatcher.ts
file defines and exports DomainEvent
, eventMap$
, and dispatch
, which we will use to create and dispatch events.
Create an Event Listener
Since listening for specific events is a repeated process, let’s create a file called useEvents.ts
and add the following code:
import { useState, useEffect } from 'react';
import { DomainEvents } from '@domain/constants/DomainEvents';
import { eventMap$, DomainEvent } from '@services/events/eventDispatcher';
type EventState = {
current?: DomainEvent;
};
const initialEventState: EventState = {};
const useEvent = (name: DomainEvents) => {
const [current, setCurrent] = useState(initialEventState.current);
useEffect(() => {
const subscription = eventMap$.subscribe(eventData => {
if (eventData === undefined) {
return setCurrent(undefined);
}
if ((name === DomainEvents.ALL || name === eventData?.name) && current?.id !== eventData?.id) {
setCurrent(eventData);
}
});
return () => {
subscription.unsubscribe();
};
}, [current, name]);
return current;
};
export default useEvent;
The code above that you just added to your useEvents.ts
file defines and exports useEvent
, which we will use to listen for specific events.
Dispatching Events
Now that we’ve created an event dispatcher, we can use it to dispatch events like so:
import { DomainEvents } from '@domain/constants/DomainEvents';
import eventDispatcher from '@services/events/eventDispatcher';
// logic for user creation should occur here.
// i.e. a request to an API endpoint, with a successful
// response back (containing the created user's ID).
eventDispatcher.dispatch({
name: DomainEvents.USER_CREATED,
data: {
id: response.body.data.id
}
});
The code snippet above is pseudocode that dispatches a USER_CREATED
event. This is pseudocode, but most of this can be modified and used in a real-world application — just remember to add logic for your specific use-case, and handle errors appropriately.
Listening for Events
Let’s look at how we can leverage our custom useEvent
hook to listen for a specific event:
import React, { useEffect } from 'react';
import { DomainEvents } from '@domain/constants/DomainEvents';
import useEvent from '@hooks/useEvents';
import { DomainEvent } from '@services/events/eventDispatcher';
const userCreatedEvent: DomainEvent | undefined = useEvent(DomainEvents.USER_CREATED);
useEffect(() => {
if (userCreatedEvent !== undefined) {
// do something
}
}, [userCreatedEvent]);
The code snippet above is pseudocode that listens for the USER_CREATED
event. When the USER_CREATED
event occurs, the useEffect
is triggered to do something specific for your use-case — since we have specified userCreatedEvent
as a dependency. Again, this is pseudocode, but most of this can be modified and used in a real-world application — just remember to modify it to your needs.
Conclusion
This tutorial is meant to be demonstrative of how to create a simple event dispatcher with React and TypeScript. This is just one way to implement this pattern, there are many other possible implementations but hopefully this gets you started. Thanks and have a great day!