Are you tired of the endless tangle of props drilling and callback chains in your React applications? Does managing state and communication between...
For further actions, you may consider blocking this person and/or reporting abuse
this looks like it will get messy quickly depending on what type of state you need to maintain.
a state management solution would be superior to this even if the state is simpler, less code and future proof in case it needs to grow.
Hi Vincenzo, thanks for your comment. Are you talking about the cloned
[count, setCount]
state management? If so it's just for example purposes and not the right use-case foruseEvent
hook, it's just to demonstrate how to communicate between components and no callbacks at all. I will change the example if that's the case as I agree it might be confusing.even if it wasn't just that, the event system doesn't really scale well for shared state.
Indeed, as I wrote at the start of the article, to manage shared state there are solutions like Zustand or Redux, the event system is intended for communication, not for sharing data.
Think about it like: "Something happened elsewhere, and I need to react accordingly".
Hope this clarify its responsibility.
I was thinking the same as him. Thought that Redux etc would be enough. But I remember you highlighted the callback chains bottom up. Thanks for the example.
Do you not need deps on the useEvent? Or is it rebinding every redraw? I have a very similar system, and needed that for it.
Thanks Mike for pointing that out, for sure something I need to add to the article.
As the callback itself is part of the
useEffect
deps array:it works like any classic hook dependency, and I suggest to memoize it else it will result in a new function at every re-render as in javascript functions are not comparable.
What I usually do is that (until React 19 compiler at least):
By doing so you will pass a memoized function to the hook, and if I'm not wrong (need to check that but pretty sure it works) the ref to that function will not change and the effect not triggered again.
Yeah that's a way! I also ended up wiring the event to my global dispatcher in a useMemo and removing it in the release function of a useEffect to get it there quickly as I raise quite a few lifecycle events and occasionally they were missed. Due to this I also capture events before the component is mounted and then dispatch them when it is.
The browser-native CustomEvent is great and is also the standard in web components. However, keeping track of those is quite hard as they're not part of a component's props object. I still find them useful but I'm scared to use them and would probably rely on using a Context.
I believe using CustomEvent even skips rendering cycles and is easier to use compared to context, but still it would be unconventional and potentially non-maintainable for me.
Thanks Alain for sharing your thoughts, in general in programming there is no best solution for everything, you need to find the best one for your needs and you personal developer experience. For me context are super good but I hate the doom they create when you have too many, and you need to create super contexts to avoid too complex context trees
You're right, Contexts can get ugly fast and I honestly don't like them much, BUT they are idiomatic to React and they're guaranteed to be the go-to first-party solution for sharing state and behavior, so it's ok for me.
About CustomEvent again, despite being great and standard, it's clearly not idiomatic in React and using a message bus that's external to fix an internal concern feels like a hack. My 2 cents
Yep you’re right, keep in mind that this solution starts from a react “problem” but extends to an architecture that you can use everywhere. Keep in mind that you don’t need to fully replace react standards as this system can help you in some scenarios, but not for every one. As always you need to balance solutions based on problems.
For example, I like to use events when you open a table item details view in a modal that overlays the table, you edit the item and you need the table to refresh. In that case events are great as you can just say “an item has changed” and the table fetch data again. These two views might be not aware of each other depending on your implementation, so don’t take this approach as the best one for every case, but for some cases
I like this approach. It's really interesting when dealing with state that's only used by a single component, but triggered from anywhere.
Maybe a good example would be status alerts, or UI notifications, since it's commonly triggered by various places but its data is only used by one component.
Many thanks. This is a great idea
Great work!
Hey there, Great article!
Would you be open to checking this out: npmjs.com/package/react-busser?
It is a package based on event-driven data communication using a hook
useBus()
. I built it for the purposes you have listed in your wonderful article. It has some building-block hooks for lists (arrays), composites (object literal) and properties (primitive types).It also has other helper hooks too that are great for UI development.
Let me know what you thick
Thanks dude, first of all, amazing job here!
There are soo many things that it will require quite some time in order to analyze everything, so don't be mad if I will take some time to review it.
Anything you would like me to check in particular? For instance code reviewing, logic reviewing or anything else?
Keep working on this!
Thanks a lot. It's okay if you take time to review it.
The more, the better i'd say.
Yes, i would love that you help me with some logic reviewing specifically on 2 codesandbox code snippets that use the package (react-busser).
This first one is an example/sample of how the package (react-busser)) works without the need to pass too many props just like in your article.
It's a simple todo app and the way it works is depicted in this image.
The concept of how the events work is what is known as (i coined this) CASCADE BROADCAST. There is a source (component) for the events implemented as a source hook and there is a target (component) for the events implemented as a target hook.
The source and target hooks send and receive events respectively.
You can play around with the simple todo app in the preview section of the codesandbox link above.
The real logic issue comes with this second codesandbox. whose link is below this line.
This is a simple e-commerce page that also uses the package (react-busser) but there's a bug when i try to filter the list of product items with the search bar.
The functionality for adding a product to the cart works well.
I would like a logic review on what you think might be the issue.
Thanks a lot for this.
.localservice@runtime.c8c182a3.js:26
Warning: Cannot update a component (
Sidebar
) while rendering a different component (Body
). To locate the bad setState() call insideBody
, follow the stack trace as described in reactjs.org/link/setstate-in-renderat Body (eventdrivearch-oc1y--5173--c8c182a...)
at div
at App
I mix of the 2 would also be a good solution, I actually use an event driven approach along with normal event callbacks, so that I can easily trigger global events like notifications, errors and easily capture them, also to decouple certain components and allow them to communicate and handle logic without needing to prop drill event handles that don't belong.
in essence you have one EventProvider that you wrap your app with, then have a context that passes methods for triggering, subscribing, unsubscribing, and have a custom hook for it called
useEvent();
A thing to consider here is if someone has your app opened in multiple tabs. I could be mistaken but that window event would reach both tabs
Thanks John, indeed I need to check that,I would prefer to keep tabs independent from each others but I need to check how does this works in that case. Anyway, the solution to that should be simple by just triggering the events within the document context instead of the window, but smth to look at
But, isn't that a good thing? 🤔 If I open a same page in two tabs (purposely or not), then if I clicked on some action (say, add to cart), I would also want the cart being updated in the idle tab. Am I imagining it wrong?
Not exactly. The problem arises when actions from both tabs start interfering with each other. For example, if you're editing a document in two tabs and save different changes in both, merging them could lead to conflicts or data loss. It’s better to keep actions isolated per tab to maintain consistency and avoid unexpected behavior.
If it comes to the state management side, wouldn't using useContext be a more effective approach to solve the decoupling parent and children?
Thanks for you comment, the event system is not intended as a state management replacement system, but as a communication between components alternative
Interesting post.
Please expand more on the pros and cons of this approach compared to using a global state library!
Thanks Daniel! But this is not intended as a global state management system, but as a communication alternative to callbacks
Bro the font family is not readable.
Thanks Harsh, you mean the graphs font?