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(); 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({ item: 0 }); const handleIncrease = (type: string) => { const randomNumber = Math.round(Math.random() * 10 + 1); eventDispatcher.emit(type, { count: ++itemsRef.current[type], value: randomNumber, }); }; return handleIncrease('item')}>Increase; }; 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(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 ( Event Data: {data ? ( Count: {data.count}, Value: {data.value} ) : ( No data yet )} ); }; export default MyComponent; 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!

Mar 6, 2025 - 13:53
 0
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();

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>;
};

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;


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!