Stop Using useEffect With Redux!
Vložit
- čas přidán 2. 06. 2024
- You shouldn't be using useEffect with your Redux store. It's splitting your business logic!
Code: github.com/jherr/redux-listen...
Course: pronextjs.dev
Where to go for project audits: www.liminal.sh/
👉 Upcoming NextJS course: pronextjs.dev
👉 Don't forget to subscribe to this channel for more updates: bit.ly/2E7drfJ
👉 Discord server signup: / discord
👉 VS Code theme and font? Night Wolf [black] and Operator Mono
👉 Terminal Theme and font? oh-my-posh with powerlevel10k_rainbow and SpaceMono NF
00:00 Introduction
01:00 App And Redux Setup
02:59 Removing useEffect
03:32 Adding Listener Middleware
05:55 Debounce In Middleware
06:19 Unit Testing
07:09 Outroduction - Věda a technologie
Just using this pattern in various projects for a team over and over, I want to refactor everything now
I've just used this on my current project, it's easier to grasp than I imagined. Thanks!
looks good, maybe a new video on managing middlewares as the app grows i think that would be great. as for this handling errors too?
Love this video, use jotai thought, I need to try it. Wondering about jotai debouncing
this is amazing and really easier to test with this pattern!!
Oooof! This one caught me off guard... suddenly I want to rebuild everything I've done last year from scratch...
The way you implemented debounce is very elegant!
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?
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.
hey jack thanks for video i have a question do we have something like this in zustand ?
Yeah, you can `subscribe` to the Zustand store directly and you'll get called back when anything changes. `useMyStore.subscribe(() => ...);`
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.
@@markerikson3383 ah right that makes sense. Thanks
Remove event listern from here, add it there. Typical react, cleaning mess with mess somewhere else.
I had no idea that was possible, that's amazing.
TanStack Router mentioned! 🙌🏼
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.
This is a very interesting way to do things that I have never considered before.
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.
export const Listener () => { HERE YOU INPUT MIDDLEWARE LISTENER, return null } Now you import that component whereever you want to attach listener, voila
@@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?
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
Is anyone you to load the middleware if you don't need it?
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 ?
No, I think even in the code it states that the ideal thing to do here would be to use RTK Query. This code is just for the purposes of the example.
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.
What event is going to run associated with “app start,” or “component mounted?”
Not all API calls can be tied to user events.
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.
a well timed info sir)
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.
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.
Thanks, Jack.
How does this mechanism integrates with RTK Query?
RTK Query sends events as actions just like everything else, so those actions could potentially trigger listeners.
It's look like inventing overcomplicated version of an effector 😁.
Does the test hit the real api or is that one result in the list mocked?
just asking it out of curiosity what is the font and theme that your using ?
the theme i think is night owl
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.
Wait, why?
Why not just wait until the search is completed and dispatch an event there?
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.
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.
*laughs/cries in redux-sagas*
This is so easy with redux sagas too! It's exactly the same backing mechanism, and gives you better granularity over your state machine.
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 ?
how will be the scenario with react query?
Why wouldn't you just dispatch inside the event handler for typing into the field? Why are we over complicating this?
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.
this is awesome.
When's the next live? I'd like to see full live on this. Too fast 😮. Can you also please show how to do this with rtkquery?
It feels like I should still use effects if I only need to to fetch once on page load, correct me if I'm wrong.
If you really want it run on app start, then just fetch it as part of the store initialization.
everybody gangsta until auth workflow with query listeners
Thank you so much.
Interesting!
I'm very guilty of this. Now I know better
What's the theme you're using in your shell?
its oh-my-zsh
@@whoman7930 I reckon oh my zsh has many themes, no?
It's the atomic theme from oh-my-posh.
thank you for this tips ! Can you do a video about React Server components? with some example
You should check out my other videos. There are a TON on RSCs and the NextJS app router.
@@jherr oki thank you for your work :) it helps me a lot !
i just dont understand, why use redux in an age of context and hooks?
I guess you have not been working on large and complex react projects?
@@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
Redux is not legacy, class components are right? From what ive read redux is used when context api is not enough @@onthefence928
Context is untenable for any state that isn't extremely stable, and requires you to scale horizontally to avoid massive performance issues
pokemon best friends forever
Im so happy to migrate to react router v6 with the loader/actions and jotai for some specific store. Redux is overkill.
cool
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
What do you think about RxJS? for me its a great choice instead of redux
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.
Final nail in the coffin for why not to use useEffect()🤣🤣🤣🤣👎👎👎👎👎😞😞😞😞😞
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.
Pokemon is back !
Ótimo vídeo.
Como ficaria isso com zustand? 🐻
Seria muito mais simples com Zustand. Você pode assinar a loja diretamente usando o método subscribe e fazer o que quiser no método subscribe.
After seeing all that boilerplate I'm so grateful for not using Redux anymore
I don't agree with this approach, i suggest using rtk query if ur using redux it makes things easier.
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.
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])
I got a even better idea, just use react query instead! You can thank me later
that looks complex and unecessary, to be honest.
Stop using react, use svelte, in first place
How about just stop using Redux....
actually i am not a big fan of functional programming and prefer to use OOP, and design patterns instead
That doesn't go with react
@@arielbatista7ify why? because people write functional?
@@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.
Or just stop using redux in the first place? :p
👍🏾 It’s overkill 👍🏾 99.8 % of the time. There 😯 many steps to go before reaching for Redux.
Exactly
Facts
This problem isn’t exclusive to redux. How does using zustand, for example, avoid this issue?
@@bass-tones 🤷 This video is on Redux. 🤔 there will be another on Zustand.
Do you think it's worth using redux in this age, I think not
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.
if you ever need a state manager then use jotai or recoil they are built for react style programming.
You can just use redux saga for that
We specifically designed listeners to be a simpler and easier-to-use replacement for sagas :)
react query simple and better, redux thunk or rtk query
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.
All is easier. Just stop using Redux
Stop using Redux.
this is a bad example. It's better to use rtk query. use a better example to avoid teaching people bad practices
Maybe back up your argument next time
This is a bad comment. It's better to express some gratitude for the free content to avoid showing people your lack of manners.
@@jherr 😂😂😂😂
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
@@grigorykuimov No worries. I also apologize for my flippant response. It's not like me. Was just having a bad day.
Stop using redux
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.
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.
rxjs epic state$.pipe(map(selector),....
Are people still using redux? 🙈
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.
People live in the fantasy world of to-do apps 😂
Yep. And proud of use it. Great library and great maintainer.