DEV Community

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

Posted on

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/

Top comments (9)

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
 
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
 
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
 
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
 
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!