Rethink State💡 Why You Should Model Events
published by Jens-Uwe Lössl on 5/23/2024
The Data-Driven World We Live In
’Data is money’—you’ve probably heard this phrase before. It’s true; data is the most valuable asset in the world today. Companies like Google, Facebook, and Amazon have built their business models around data.
Applications, whether web or mobile, revolve around data: displaying it, processing it, and sending it to backends or external systems. The challenge lies in managing data efficiently to provide seamless user experiences.
The CRUD Approach vs. Event-Driven Architecture
Many web applications follow a CRUD (Create, Read, Update, Delete) approach: you fetch a resource from the backend, allow the user to modify it, and send the updated resource back. This results in UI structures built around data operations—list views, create/update forms, and delete actions. For example, if you’re building a blog, a CRUD mindset means focusing on creating, updating, and deleting posts.
However, humans don’t think in terms of updating data. We use applications to accomplish tasks quickly. Wouldn’t it be more intuitive to model the application around tasks instead of data manipulations? For example, instead of manually updating post statuses, you could display a list of unpublished posts with a ‘Release’ button to quickly publish them.
Modeling Around Events
Starting with Commands
To simplify the shift in thinking, let’s introduce a small trick: separate UI interactions from UI state. Instead of directly modifying data, the UI should only dispatch commands—user intents that trigger backend logic. Consider this example of emitting an event to a global RxJS subject.
import { Subject } from 'rxjs';
const GlobalEvent$ = new Subject();
class ReleasePostEvent {
public readonly type = 'POST_RELEASE_COMMAND';
public readonly payload = new ReleasePostPayload();
}
class ReleasePostPayload {
public postId: string;
}
const UiComponent = ({ postId }) => {
const handleReleasePost = (postId: string) => {
const event = new ReleasePostEvent();
event.payload.postId = postId;
GlobalEvent$.next(event);
};
return <button onClick={() => handleReleasePost(postId)}>Release</button>;
};
What Happens to These Events?
This is where the magic happens: you can handle events however you like. 🚀
Instead of tightly coupling state changes to UI actions, events allow for greater flexibility. Here are some possibilities:
- Update stored models on the server.
- Send events to analytics tools like PostHog.
- Log events to the browser console during development.
Imagine a customer requests a feature: “When X happens, can you also do Y?” With an event-driven model, you’re already prepared to handle it efficiently.
Combining Events for Smarter UI State
Consider a loading indicator: in a CRUD-based system, you typically set a boolean
flag in the application state. With events, you don’t
need explicit state management. If a ‘loading’ command is dispatched and a corresponding ‘resource loaded’ event hasn’t been received yet,
you know the application is still loading. The logic could look like this:
import { filter, map } from 'rxjs';
const isLoading$ = GlobalEvent$.pipe(
filter(event => event.type === 'LOADING_COMMAND' || event.type === 'RESOURCE_LOADED'),
map(event => event.type === 'LOADING_COMMAND')
);
Querying Data Efficiently
When designing applications, it’s crucial to separate querying data from emitting commands. Fortunately, modern technologies allow pushing updates from servers to clients, making data retrieval seamless. A repository pattern might look like this:
import { Observable } from 'rxjs';
interface IPostRepository {
readonly Posts$: Observable<Post[]>;
readonly Post$: Observable<Post>;
loadPost(postId: string): void;
loadPosts(): void;
}
Your UI components can subscribe to Posts$
to reactively display data as it updates.
When Should You Use Event-Driven Architecture?
Not every application needs an event-driven approach. If you’re building a static website with minimal user interactions, a CRUD-based architecture might suffice. However, consider using event-driven architecture in scenarios such as:
- Applications with high user interactions (e.g., dashboards, collaborative tools).
- Systems where multiple sources modify application state (e.g., real-time messaging apps).
- Applications requiring reactive state management and asynchronous processing.
By rethinking state management and structuring applications around events instead of direct data manipulations, you can build more scalable, flexible, and maintainable frontend architectures.
Conclusion
Moving beyond CRUD-based thinking and embracing event-driven architecture enables applications to be more responsive and adaptable. By focusing on what happens instead of how data is updated, you create a system where business logic is decoupled from UI interactions, making your frontend more powerful and resilient.