DEV Community

Noyan Dey
Noyan Dey

Posted on

Ditch Props Drilling! A Smarter Way to Manage State in React

State management in a React application can sometimes be tricky, especially when we need to share state between components that do not have a direct relationship. Typically, developers use props drilling, the Context API, or state management libraries like Redux or Zustand to handle such cases.

But what if we could avoid all of these and still effectively manage state across components?

The Tweak: Leveraging Browser's Built-in Events

Instead of using a dedicated state management solution, we can utilize the browser's built-in event system to handle state updates globally. This approach provides a simple, lightweight alternative to manage state updates across unrelated components.

Step 1: Setting Up an Event Dispatcher

To facilitate communication between components, we can use the eventemitter3 package, a lightweight and efficient event emitter for JavaScript and TypeScript applications.

// utils.ts

import EventEmitter from 'events';

export const eventDispatcher = new EventEmitter();

Enter fullscreen mode Exit fullscreen mode

Step 2: Emitting Events

We can use the eventDispatcher to fire events when state updates occur.

Action.tsx

import { useRef } from 'react';
import { eventDispatcher } from './eventDispatcher';

export const ActionButton = () => {
  const itemsRef = useRef<{ [key: string]: number }>({ item: 0 });

  const handleIncrease = (type: string) => {
    const randomNumber = Math.round(Math.random() * 10 + 1);

    eventDispatcher.emit(type, {
      count: ++itemsRef.current[type],
      value: randomNumber,
    });
  };

  return <button onClick={() => handleIncrease('item')}>Increase</button>;
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Listening for Events

To receive updates in another component, we subscribe to the event using useEffect.

// MyComponent.tsx

import { useEffect, useState } from 'react';
import { eventDispatcher } from './eventDispatcher';

const MyComponent = () => {
  const [data, setData] = useState<{ count: number; value: number } | null>(null);

  useEffect(() => {
    const handler = (payload: { count: number; value: number }) => {
      setData(payload); // Updating state to trigger a re-render
    };

    eventDispatcher.on('item', handler);

    return () => {
      eventDispatcher.off('item', handler);
    };
  }, [eventType]);

  return (
    <div>
      <h3>Event Data:</h3>
      {data ? (
        <p>
          Count: {data.count}, Value: {data.value}
        </p>
      ) : (
        <p>No data yet</p>
      )}
    </div>
  );
};

export default MyComponent;


Enter fullscreen mode Exit fullscreen mode

πŸš€ If you encounter any issues with the built-in event package, consider installing eventemitter3 for a smoother experience!

πŸ’‘ You can also review the codebase here: Github Repo

Why Use This Approach?

  • βœ… No props drilling – Avoid passing props through multiple layers of components.

  • βœ… No Context API – No need to wrap components with a Provider.

  • βœ… No state management libraries – Simple and lightweight solution.

  • βœ… Decoupled components – Components do not need to know about each other, making the architecture cleaner.

Final Thoughts

While this method provides an easy way to share state across components, it should be used carefully. It is best suited for lightweight state-sharing needs, such as broadcasting events, handling notifications, or managing global UI states.

For complex applications with frequent state updates and dependencies, dedicated state management solutions like Redux or Zustand might still be preferable.

Give this tweak a try in your next React project and see how it simplifies your state management! πŸš€

Top comments (0)