This looks kinda complicated. Is there a benefit of doing this over using a hook which contains all this fetching and updating logic and can be shared across components? I know you mentioned keeping all the store logic in the store but imo this method isn't very intuitive. And either way in an actual project we'd probably be using something like react query or something similar anyway.
Separation of concerns and the ability to fully unit test the store without also testing the UI at the same time are pretty sizable benefits. Technically speaking lots of stuff "works", but I personally like to set the bar higher than "working" and into well factored and organized code with clear separation of concerns.
Different tools, different needs. Sometimes you do need truly "global" app behavior that isn't isolated to one component or isn't limited to a component lifecycle, and that's what listeners give you.
Thanks, I liked the vide because I learned a new concept/technique in Redux Toolkit (which is by the way my favourite State Manager for React), but I didn't run into this anti-pattern in my work projects because I did not save the query information on the store, I consider this a quite bad design choice, anyway, this video will be useful in case I find myself obliged to handle a case like shown.
we don't change redux store by firing action inside useEffect but we trigger the middleware by firing action inside the useEffect, what do you think is it also anti pattern? Unfortunately we can't trigger middleware the way you are doing because we want to trigger it only when specific page loads, so we fire the action inside the useEffect to run the middleware only when certain page loaded.
@RealRhythmandPoetry didn't really get what you meant. what we do here is, we have a middleware which listens to action named `homePageLoaded` and we trigger this action only once inside the useEffect when our HomePage component gets rendered for the first time. Now how we can get rid of useEffect in this case, how can I trigger this action only once and only when my HomePage component gets rendered?
Thanks for video Jack. I prefer fetch api on event handler. It makes more sense to me dispatch actions which cause side effects should be deal with user interaction handler.
Do what works for you. The point of this video was to cover an often-overlooked option in Redux toolkit. Though I will say, the unit test case is legit here. If you want clean separation of concerns between the business logic and the presentation logic, this listener functionality provides another clean way to do that.
I feel like this scales pretty poorly if there's anything non-trivial in our predicate when comparing old vs new state? We scale with order N comparison functions that have to run after every action. It feels like we'd want to strongly prefer the action-style predicate because it's guaranteed to be cheap.
Maybe it needs something like listenerApi.cancelActiveListeners() before delay to act like a 'real' debounce, e.g. restarts the delay on subsequent keypresses while the delay is still running?
I use rtk for a large application I’m building, is it the best probably not but to think people wouldn’t use something anymore just because it’s not trendy is kind of ridiculous.
not readable code for bigger projects. I do not understand, why i schould load the middleware if i do not need it currently. It depends on what u need in your code. Thank you for this way of using middleware
It's just an example to show use of the listener. You don't need to use it in this use case. But now that you know it's available you might be able to find other uses for it. FWIW though, I have seen code that looks an awful lot like what I showed here, in the wild.
It is a nice clean solution but I don't see how this solution scales to the scale that many codebases that use redux need. Would every slice have a middleware also?
That entirely depends on you and your application. Listeners are there to add side effects, so the real question is how many of your slices also have associated side effects? Beyond that, note that data fetching should really be handled by RTK Query, not listeners. Jack showed off a listener that did data fetching as a small example, but he did say "we're intentionally _not_ using RTK Query" here. I would guess that most slices do _not_ have much in the way of associated side effects.
This is a simple example just to demonstrate this overlooked feature of RTK. I've seen a lot of code like the example here. It's very common for folks to use `useEffect` whenever they have async behavior that they need in their store.
Hi Jack! Have you used jotai in large production apps? How does it compare to zustand and the likes? I really like jotai and couldn't find a reason to move away from it
Meta uses Jotai/Recoil in their applications. You could probably call those large. Personally I find Zustand a little easier to reason about. The data and "methods" are organized together. You can persist it as a unit. etc. Where Jotai is a graph with data dependencies. I think in one way Jotai scales better because you don't end up adding more data and dependencies to a store. But on the other hand, the graph could get out of control. Either way this really comes down to architecture and code reviews and actually caring about the code. As long as you do that, you're probably ok using either approach.
@@Edgars82 I’m an enterprise react developer and I’ve actually been working with my team to remove redux from a legacy project to modernize it with hooks instead.
@@onthefence928 because context still has no mechanism to selectively subscribe to a portion of the context value, so you either end up with a million contexts or components rerendering unnecessarily
As mentioned in the video, RTK query is the ideal. I agree. I intentionally decided not to use it so that I could show this feature instead. This feature is NOT a replacement for RTK Query. It can be used in conjunction with RTK Query.
so what if RTK Query is involved? Should we still include that listenerMiddleware? Also I'm not sold on this approach since it's also just another kind of side effect listener and those tend to get to the point of unmaintainability really quick. The action of getting value + debounce should be in the UI file in a handleInput or something, you then delegate the api call + store update into another function
With rtk query, you'll not need this 98% of the time. I disagree with you on the debounce. You want to debounce the request, not the user interaction, ergo that kind of logic has no place in the UI layer, it should go in the data layer of the application.
@@pataroque normally you want to debounce the action of causing the request, not the request itself. (For an search input, the text still shows as the user types, but the API call will be deferred. This is common practice). Then the problem comes down to where do you want to put down this logic. From my perspective, no side effects functions are ever easy to maintain or work with, whether you put that in useEffect or redux's thing. Just for this simple pokemon search this demo demonstrated already involved some really niche functions and wayy more code lines then needed. Imagine this for 10 or more complex forms.
Would've been exhaustive if the initial state setting was done without tanstack router's method A lot of apps don't use it/ What's the normal way of doing it ?
I see a really similar pattern used with react query, people will make a useEffect depend on a value from a query hook variable isLoading, isFetching, data etc. useEffect(() => { //do something to the data }, [query.data])
If we would like to use rtk query for that case we would use a lazy query and call it whenever search term changes ? In that case an useEffect would be necessary ?
@@jherr Yes, but you would still have to use the triggerPokemonSearch callback returned from useLazyQuery inside useEffect or the change handler. I think that’s what he’s asking.
IMHO, I would not use it as a state manager in React. Angular currently uses it and they are moving away from it. It's an awesome library. Unfortunately there is just a lot of congnitive overhead for little gain. There are simpler state managers that provide just as much functionality.
@@jherr I agree with you on the cognitive overhead, but for large, complex, monolithic apps, the set of operators and abstractions Rxjs provides is unparalleled. Angular is moving away from it, but essentially is just simplifying the dev experience with signals. IMHO, both paradigms have the same goal - making the UI truly reactive, and I prefer them both over stores. In observables/signals you push data to a stream, and the data source does not care, who's listening. With stores you pull the data, and there's no knowing if you got the correct state at the moment of asking.
Nah, if we can create a listener on a go when the component mount & remove it when the page does not use it any more then I will probably use this. The pattern is created so people can follow the common case but not to follow it blindly
You can absolutely use useEffect if you're not moving out your reactive logic out of the React tree, as you tend to do it with Redux and other client-side state containers. But it's simply better to use a query container and you're fine with occasional useEffect logics.
@@mostafahamid9479 is design to be written with functions, the funtional components and the hooks, that would be half of the app, then the state manager, most of then also functional, so what part will you write with oop? Wouldn't make sense to write the remaining parts also with functions?
Gotta agree with @arielbatista7ify on this one. Idiomatic React is far more of a functional programming paradigm than an OOP paradigm. Certainly since the release of hooks. Class components have effectively been deprecated for years. The new docs hardly mention them outside of Error boundaries, which are the only time you'd ever need them. And I can think of a single state store that is built on classes. I'm not saying that's good or bad, it just is what it is. If you try to write React in an OOP style you are going to be swimming upstream. Angular (?) may be more aligned with OOP.
@@codefinity I realize. But the topic of this video is NOT a reason to not use Redux. In fact, unlike something like zustand, at least Redux offers a solution to “effect”-like code at the store level. Doing this in any of the minimal store libraries would require a completely custom solution, or falling back to something like useEffect like this video discusses, which I agree is a bad idea. If anything, this is a point in Redux’s favor that its structure allows for concepts like listeners.
I've played with it, but haven't used it. I don't see a lot of projects opting for it and instead going with Redux/RTK or lighter weight options like Zustand, or atomic solutions like Jotai/Recoil. I'd stay away from MobX/Valtio as they are incompatible with the new React Compiler.
IMHO, it's a well maintained state library with a great ecosystem, a massive install base, a healthy weekly downloads (9M) and a huge number of developers working with it. It's also very well maintained and it keeps up with the standards. So those are a lot of positives. That being said, I think there are legitimate cases where no state manager at all is required. Or where you can use something lighter weight, like Zustand or Jotai/Recoil. There is no single "right answer" here, you have to evaluate state management and project architecture choices in the broader view of all kinds of factors.
Yeah, I think I mentioned that as I was talking and also in a comment in the code. This is a simple example from which you should be able to extrapolate your desired behavior.
Great tutorial, as usual. Global stores are supposed to be distributors. However, this listener seems to be bringing some responsibility to the store. Am I missing something?
Hmmmm, I personally don't think of stores that way. I look at the store as the Model and Controller in the Model-View-Controller paradigm. It should contain global data as well as the business logic to maintain that data. And you should be able to have unit tests on just the store that test whether mutations run against the store follow the business logic in updating the store. If that's not the case, and you have business logic in the UI, then what you have with the store is effectively a fancy global variable.
I appreciate you for the other videos. Especially the explanations of react-compiler. In this case I saw how redux was not showing its best side. Sorry for my comment above
Jack's goal was to show off the fact that the listener middleware exists, and the basic mechanics of how to use it. He did specifically note that in a real app, you should use RTK Query for the data fetching part, but the "fetch on user input" example worked nicely to show the basic mechanics _and_ that you can do async behavior like the equivalent of a debounce.
You don't always get the chance to pick your libraries. Sometimes you have to maintain existing code and augment it in the least pervasive way possible. This is a very good example on how to do that particular use case on an existing redux codebase.
Personally I don't use a lot of Redux, but many many... many... codebases do. In fact, the majority of code bases I've worked with and audited use Redux. And many of them use this anti-pattern for using async code with their Redux stores.
9M downloads a week, and though it has flatlined a little in 2024 it's still chugging along. Redux came along a couple of years after React was released and there is a lot of ecosystem around it. Lots of folks use it and it's well maintained and keeps up with the changes to React over time. I personally don't use it for my projects. I tend to just use hooks. Or use something like Zustand or Jotai. But I can't deny that there is a LOT of Redux code out there. Thus I cover it.
Sure our 250 developers project that’s in development for years will just throw out redux because Zustand is hype of the week, after that let’s just rewrite everything to jotai?
@@jherr Exactly. I use hooks. Every senior frontend developer i know use hooks. We all tried Redux between 2016 and 2020 and we all concluded it was a global state beast that results in bad frontend architecture. Data should be scoped to the context boundary its needed. Redux just pushes all data in a big state tree mess. Suddenly you are afraid to remove or change reducers because the you are unsure what components are affected by the global state. Can also add on the issue with "memory leak" issue since the redux store is global and if components dont clean up after them self there are lot of dead data in the global store eating up memory in the browser, so you need to enforce strickter code reviews to ensure obsolete state is always removed.