DEV Community

Cover image for Managing Application State with Custom Events in React: A Simple Yet Powerful Approach
Adrian Knapp
Adrian Knapp

Posted on

64 1 3 4 2

Managing Application State with Custom Events in React: A Simple Yet Powerful Approach

When building React applications, managing state across components can become challenging. While solutions like Context API, Redux, or Zustand are popular choices, there's a simpler native browser feature we can leverage: Custom Events.

What are Custom Events?

Custom Events are part of the native Browser Event API that allows you to create and dispatch custom events throughout your application. They provide a lightweight, decoupled way to communicate between components without prop drilling or complex state management libraries.

Why Use Custom Events?

  1. Native Browser API - No additional dependencies are required.
  2. Decoupled Communication - Components can communicate without direct relationships.
  3. Simple Implementation - Easy to set up and maintain.
  4. Performance - Lightweight alternative to global state management.
  5. Type Safety - Can be fully typed with TypeScript.

Implementation Example

Let's look at a practical example of implementing a modal system using Custom Events.

1. Define Your Custom Events
First, create an interface to define your custom events:

export interface CustomEvents {
  'modal-event': {
    action: 'open' | 'close'
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Create a Trigger Function
Create a helper function to dispatch custom events:

export const triggerCustomEvent = <EventName extends keyof CustomEvents>(
  eventName: EventName,
  data: CustomEvents[EventName]
) => {
  const event = new CustomEvent(eventName, { detail: data })
  document.dispatchEvent(event)
}
Enter fullscreen mode Exit fullscreen mode

3. Create a Custom Hook
Create a hook to listen for custom events:

export function useEventListener<T extends keyof CustomEvents>(
  eventName: T,
  handler: (detail: CustomEvents[T]) => void
) {
  useEffect(() => {
    const eventHandler = (event: CustomEvent<CustomEvents[T]>) => {
      handler(event.detail)
    }

    document.addEventListener(eventName, eventHandler as EventListener)
    return () => {
      document.removeEventListener(eventName, eventHandler as EventListener)
    }
  }, [eventName, handler])
}
Enter fullscreen mode Exit fullscreen mode

Usage Example

Here's how to implement a modal system using Custom Events:

Triggering the Modal:

const handleOpenModal = () => {
  triggerCustomEvent('modal-event', { action: 'open' })
}
Enter fullscreen mode Exit fullscreen mode

Modal Component:

export const FeedbackModal: React.FC = () => {
  const [isOpen, setIsOpen] = useState(false)

  useEventListener('modal-event', ({ action }) => {
    switch (action) {
      case 'open':
        setIsOpen(true)
        break
      case 'close':
        setIsOpen(false)
        break
    }
  })
Enter fullscreen mode Exit fullscreen mode

Real-World Benefits

Let's examine a practical scenario where Custom Events shine. In our example application, we have three separate components (Header, Content, and Footer) that all need to trigger the same modal:

export default function Home() {
  return (
    <div>
      <Header />
      <Content />
      <Footer />
      <FeedbackModal />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Instead of:

  • Lifting state up to a common parent
  • Using Context API
  • Implementing a state management library
  • Prop drilling

We simply:

  1. Define our custom event
  2. Trigger it from anywhere
  3. Listen for it where needed

Best Practices

  • Type Safety: Always define your custom event interfaces
  • Event Naming: Use clear, descriptive event names
  • Cleanup: Always remove event listeners in useEffect cleanup
  • Payload Structure: Keep event payloads simple and well-defined

When to Use Custom Events

Custom Events are particularly useful when:

  • Components need to communicate across different parts of the application
  • You want to avoid prop drilling
  • You need a lightweight alternative to global state management
  • Components need to react to actions without direct relationships

When Not to Use Custom Events

Consider alternatives when:

  • You need persistent state
  • You require state synchronization across multiple tabs
  • You need to handle complex state logic
  • You need to track state history

Conclusion

Custom Events provides a simple, powerful way to handle component communication in React applications. While they might not replace full-state management libraries for complex applications, they offer a lightweight solution for many common scenarios.

The beauty of this approach lies in its simplicity and use of native browser features. It's a reminder that sometimes the best solutions are the ones built into the platform itself.

This implementation demonstrates how we can leverage browser APIs to create clean, maintainable code without unnecessary dependencies. The full example code is available in the provided repository, showing how Custom Events can elegantly solve cross-component communication challenges.

Repo: https://github.com/AdrianKnapp/custom-events
Try it out: https://custom-events-iota.vercel.app/

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (16)

Collapse
 
gunslingor profile image
gunslingor • • Edited

Just an FYI, the event paradigm in react becomes highly problematic. It's just as bad as combining angular and react in a real world application. They know nothing of each other and you end up with the worst of both worlds. Architecture is the solution, but this solution becomes an uneconomical problem, as it usually does when you bypass the original intent and integrations your main framework vendor intended you to use.

That being said, it is a good implementation since it's isolated, I usually see all this inside the main 800 line react function, lol. Really though, they should be converted to state hooks and lifted if appropriate. Remember react has a virtual dom, not the browser dom... your editing the browser dom there I think without telling react... so the react state becomes invalid. Worst, human devs end up having no idea what's happening. I love events... but if you use em, definitely you shouldn't be using react... they work very well with pure js, very bad with react, thee hath been warned... one need not two systems of managing events.

Collapse
 
traviselam profile image
Travis Elam (wisconsinite) • • Edited

New devs should pay attention to this ^ comment

Collapse
 
genexu profile image
Gene Xu •

True, the first thing I learned when I was React noob developer is always to keep state and VDOM consistent with React itself and don't try to control elements without React data flow.

Collapse
 
ansa70 profile image
Enrico Ansaloni •

I used this approach years ago on an old class based React app, here's a few things I learned:

  • it's a good thing to create a singleton type class to handle global events dispatch and handling
  • be very careful to unsubscribe from events before the component lifecycle ends or you'll end up with nasty side effects and possibly memory leaks
  • create an utility class to handle event subscription/unsubscription in a common centralized way so you can use it in all components and have a logging/debugging singular point
  • create structured types and/or Interfaces to pass as events for a more robust approach
Collapse
 
adrianknapp profile image
Adrian Knapp •

Well said, @ansa70! All these points are very important. I think I covered each one in my implementation. Did you notice if I forgot something?

Collapse
 
ayesha_malik_e82fcf402a94 profile image
Ayesha Malik •

Great breakdown of using Custom Events in React! 🚀 This is a smart alternative to prop drilling and global state management. The simplicity and native API usage make it super efficient. Thanks for sharing!

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him) •

Maybe you'd want to take a look at useSyncExternalStore

Collapse
 
adrianknapp profile image
Adrian Knapp •

Nice, thanks for the recommendation, I'm gonna take a look!

Collapse
 
xyzt70 profile image
Mohamad Yahia • • Edited

This works for smaller/hobby apps but it's much easier to scale if you just use zustand as the single source of truth.

Collapse
 
matheusrufca profile image
Matheus Rufca •

You may need to add some complexity if you want to handle multiple same-name events in the same component.

Collapse
 
reactoholic profile image
Petar Kolev •

Dude, do not trick people to fall in this trap... React is not Node. Event driven React is one of the most React ani-pattern things to implement and it's hell to debug in a bigger app. I've been working on such app and believe me, I'm not going there again.
Nice of you to put effort in this article but I highly recommend you to research very good what you want to write about before posting it.

Collapse
 
jeanluca999 profile image
Jean •

Interesting, even though I knew browser custom events, I never considered the potential to use them to handle state in React apps, it's the kind of knowledge that is worth having in my pocket

Collapse
 
adrianknapp profile image
Adrian Knapp •

Absolutely, Jean! It's very useful and you can customize whatever you want. Glad this helped you, thanks for reading!

Collapse
 
alveshelio profile image
Helio Alves •

I don't see any added benefits, only drawbacks. @gunslingor described really well all the problems you will encounter with this approach.
You are basically re-implementing React's internals.

If you want to go with this approach you shouldn't be using React.

Collapse
 
netssrmrz profile image
Esteban Ramirez •

Good article. Custom events are a powerful mechanism that I use all the time. It's sad to see React fanboys refer to this as an anti-pattern. You're missing out guys. There's plenty other DOM concepts that React components don't support, like simple properties and methods. This shouldn't be considered a feature. It's a limitation. Don't believe the React hype that it's for your own good. Bad devs will make a mess of anything. Even React.

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series