There are some points in life, where you encounter a case so unique, so different, so unexpected — that the solution just has to be special.
We use an everything-is-a-module approach, alongside micro-services everywhere in our company.
This usually is a good place to be in.
Yet sometimes you reach to a point where you component being a module is just too much.
In your website, you have some live tweet viewers.
The tweet viewer auto updates, and has a specific server for data fetching.
The server API endpoint returns all tweets together.
Each tweet viewer is a standalone module, imported from and installed react library you hold.
Cool.
Now, you want to use multiple tweet viewers on screen, while keep calling just one fetch for all of them.
- How can we call an inner function of one component, and update all others from it?
- How can multiple components communicate each other, without using the parent?
After all, events were invented for a reason.
It helps us communicating between different aspects of the web application.
User interaction events help us connect UI to logic and create behavior.
So why don't we use this mechanism to connect our multiple UI components to data, to create one-way data binding?
Recipe:
- react tweet viewer components
- data event router
- custom event handler
- async data fetcher
1import React, { useEffect, useState } from "react";
2import { EventHandler } from "../logic/event-handler";
3
4export default function TweetViewer({ tweetId }) {
5 const [tweetData, setTweetData] = useState({});
6
7 useEffect(() => {
8 const handler = (event) => {
9 setTweetData(event.detail);
10 };
11 EventHandler.subscribe(tweetId + "::data", handler);
12 // Cleanup if needed
13 return () => {
14 window.removeEventListener(tweetId + "::data", handler);
15 };
16 }, [tweetId]);
17
18 const { author, tweet } = tweetData;
19 return (
20 <div className="tweet">
21 <h4>author: @{author}</h4>
22 <p>tweet: {tweet}</p>
23 </div>
24 );
25}
26
27
1import React, { useEffect } from "react";
2import ReactDOM from "react-dom";
3import TweetViewer from "./components/TweetViewer";
4import { EventRouter } from "./logic/event-handler";
5
6function App() {
7 const tweetIds = [1, 2, 3, 4, 5];
8 useEffect(() => {
9 const router = new EventRouter();
10 router.fetchTweets(tweetIds);
11 }, []);
12
13 return (
14 <div className="App">
15 {tweetIds.map((id) => (
16 <TweetViewer tweetId={id} key={id} />
17 ))}
18 </div>
19 );
20}
21
22const rootElement = document.getElementById("root");
23ReactDOM.render(<App />, rootElement);
24
1class CustomEventHandler extends EventTarget {
2 subscribe(eventName, cb) {
3 window.addEventListener(eventName, cb);
4 }
5 emit(eventName, payload) {
6 window.dispatchEvent(new CustomEvent(eventName, { detail: payload }));
7 }
8}
9
10export const EventHandler = new CustomEventHandler();
11
12export class EventRouter {
13 constructor() {
14 this.eventHandler = EventHandler;
15 }
16
17 fetchTweets(tweetIds) {
18 setInterval(() => {
19 tweetIds.forEach((id) => {
20 this.eventHandler.emit(id + "::data", {
21 author: randomString(10),
22 tweet: randomString(),
23 });
24 });
25 }, 3000);
26 }
27}
28
29// mimic response
30function randomString(length) {
31 const strLength = length || Math.random() * 100;
32 return Math.random().toString(36).substring(strLength);
33}
34
