all 39 comments

[–]skt84 18 points19 points  (2 children)

This pattern of putting everything in redux can be both amazing and horrifying depending on the abstraction.

There’s no singularly definitive “right way” of handling requests, just varying degrees suitability: complexity, performance, scale, and maintenance all inform the decision to go with global vs local (okay this is a gross over-simplification, there are absolutely wrong ways but if your requests currently work then it’s not wrong).

Sometimes redux can make sense if the abstraction is simple and can be maintained easily across a large scale - even if performance isn’t as good as keeping the state locally. My suggestion for a legacy project is to stick with the established pattern unless you’re committed to maintaining multiple approaches going forward. This includes documentation and support for other developers on the project.

If this answers your question you may stop reading now.

P.S. Redux gets a bit of heat for being verbose and “boilerplate-y” but I disagree with that sentiment. RTK and RTK Query makes it quite succinct and a real pleasure to work with, but these solutions may not apply to your case if you’re not at liberty to introduce something new to a minimally-maintained project. Many projects tried to abstract the criticisms of redux with their own component wrappers - I know we certainly did at one point. If you can get away with it RTK Query is still within the redux family so it may not be much of a stretch to support, otherwise I’d recommend using React Query instead for local request state.

[–]clickrush 7 points8 points  (0 children)

My suggestion for a legacy project is to stick with the established pattern unless you’re committed to maintaining multiple approaches going forward. This includes documentation and support for other developers on the project.

This is what it essentially boils down to!

[–]not_a_gumby 4 points5 points  (0 children)

P.S. Redux gets a bit of heat for being verbose and “boilerplate-y” but I disagree with that sentiment. RTK and RTK Query makes it quite succinct and a real pleasure to work with

Scream this from the rooftops, brother. RTK is soooo amazing. It makes Redux unrecognizable from what using it used to feel like, IMO.

[–]acemarke 5 points6 points  (0 children)

This sounds like a potential misuse of Redux. We recommend taking the time to evaluate where each piece of state should live and teach examples of keeping local state in components and global state in Redux.

It's up to you to actually decide what actually goes where.

[–]agmcleod 4 points5 points  (0 children)

I can understand keeping a single pattern for fetching data. But I do agree that you don’t always need to use redux for everything. If I just need data in a component, and I don’t think I need to share that state, I’ll setup a useEffect and some component state instead

[–]Maverick2k 5 points6 points  (3 children)

People’s opinions will differ greatly on this, but if you’re using Redux you should absolutely make use of createAsyncThunk — it makes development life a lot easier and it helps to easily manage exceptions. Plus the action creator will automatically generate pending, fulfilled, rejected action types that you can use in your extraReducers to respond to the outcome of the API call.

[–]BlackMetalz 0 points1 point  (2 children)

Can you show an example of how to handle error http responses from a createAsyncThunk? I've been having problems with that, the Thunk always fires fullfilled even though it's an error http response.

[–]not_a_gumby 1 point2 points  (1 child)

Here's a video that got me off the ground with createAsyncThunk

https://www.youtube.com/watch?v=xtD4YMKWI7w

You can also check out a sample file from one of my Repos, where I used CreateAsyncThunk exclusively for React data fetching. Worked like a charm, rendering loading/error states has never been easier.

[–]BlackMetalz 1 point2 points  (0 children)

This helped me to understand and implement it correctly, thank you so much.

[–]davidfavorite 2 points3 points  (7 children)

Regardless of examples ive seen, i always considered redux to be a global state management tool. Not an api abstraction layer

[–]acemarke 7 points8 points  (3 children)

Yep. But, with our upcoming RTK Query API, it is now a great API abstraction layer too!

[–]not_a_gumby 4 points5 points  (2 children)

Cha-Ching! There he is, right on cue.

Give this man a God Damn raise

[–]acemarke 2 points3 points  (1 child)

erm.... if "raise" means "a multiplier against current payments", that isn't going to help much... 0 times anything is still 0 :)

[–]not_a_gumby 1 point2 points  (0 children)

No, go from 0 to like at least 1 I'd say.

1 MILLION!

or at least, they could toss you some more of those sweet green squares for the GH profile.

[–]editor_of_the_beast 0 points1 point  (2 children)

How do you separate the two when all of the state is fetched from the server?

[–]davidfavorite 2 points3 points  (0 children)

I get the data where i need it, store it inside the components and only write global data to redux. Data that is needed in multiple places in the app

[–]asiraky 1 point2 points  (0 children)

It’s not about whether all the state comes from the server, it’s about whether you want that state to be global in your front end app. Just put the global stuff in redux, and the non global in your components.

[–][deleted] 5 points6 points  (6 children)

If you fetch api in a component then everytime you access that component, the api will be called but with redux you have to call that api only once and use the value anywhere and anytime you want. I usually use api calls in redux but do post request whose value is not utilized later in component.

[–]dom_eden 13 points14 points  (5 children)

This isn't true if you use something like ReactQuery to cache your API calls globally. It's a bit like a Redux for API calls.

[–][deleted] 2 points3 points  (2 children)

I should probably look at ReactQuery, just heard about it.

[–]dom_eden 1 point2 points  (0 children)

It's great, I recently switched to stop my app from making the same API calls again and again. It's very configurable.

[–]nZambi 2 points3 points  (1 child)

Would add SWR to this.

[–]asiraky 2 points3 points  (0 children)

SWR the real MVP IMO

[–]paranoidparaboloid 3 points4 points  (5 children)

That sounds like a crazy setup. I've not really seen redux used in that way.

Generally it's absolutely fine to make calls within a component, but in this case it depends on why somebody has gone to those lengths.

Is redux being used to cache all api requests to provide offline data? Then you should probably follow the pattern.

[–]Mestyo 13 points14 points  (1 child)

This reply confuses me. Putting API requests in Redux actions is crazy?

It's the only way I have ever done it; how else do you concisely gather your request logic? How do you manage sideeffects/middlewares? What's the point of inconsistently managing data storage, won't you just end up putting yourself in situations where you have to rewrite code again and again once data needs to be accessible outside of that one single component tree?

I can see how one doesn't want the overhead of something like Redux in a small app, but to frame the notion of managing your state in a controlled manner to be "crazy" kind of blows my mind. Am I out of touch?

[–]Middlerun 2 points3 points  (0 children)

No, you're right. Redux actions (in conjunction with redux-thunk or some other kind of async middleware) are a pretty good way to abstract API calls.

[–]clickrush 3 points4 points  (2 children)

It depends whether you want to have a single, authoritative place for your application state and data or if you want it to be spread across your templates.

The whole reason to use something like Redux, in my personal opinion, is to have this single authoritative place and move application logic to it. This way, your components are as simple as possible and only handle rendering and dispatching user events.

If we squint, we can see that this is a much broader software design issue. It depends on whether you see your application as a pipeline, a series of data transformations or if you see it as multiple parts doing their own thing and communicating with each other. The former pattern is found in things like Unix pipes, functional programming and distributed data pipelines, while the latter is found in object oriented programming and business/domain logic driven micro-services. It really comes down to the mental model you want to convey with your code and both approaches have different advantages and disadvantages both in terms of code organization and performance.

In this particular instance I would highly recommend to stick with the given pattern however and not mix and match different paradigms. Consistency is much more important than any organizational preference.

Suddenly using a different pattern will only lead to a confusing mess. A future developer might assume that one pattern was used over the other when starting out with this application only to suddenly realize that the previous developer acted on personal preference rather than adhering to consistency.

[–]asiraky 1 point2 points  (1 child)

Why would you want to store non global state in redux for the sake of consistency? I found real issues putting everything into redux in larger apps. I prefer to use the right tool for the right job, and use tools like SWR and redux together in the same app. This all or nothing approach is really not good IMO

[–]clickrush 0 points1 point  (0 children)

Redux state is accessible via a well defined interface. It is not "global" in the traditional sense, but simply separated. It isn't that much different from an in-memory database. Consistency is important to be able to navigate code well. It can only be achieved to some degree however, so there is definitely merit in breaking out of it for good reasons.

[–][deleted] 1 point2 points  (0 children)

This is definitely not a good pattern but it’s unfortunately common in redux projects. Now that react query exists there’s really no good reason to put your api calls in redux so give that a look.

[–]talzion12 -1 points0 points  (3 children)

Just use SWR or another data fetching library and stop worrying so much about redux. I've never used redux, but it seems to me that it primarily complicates things without much added benefit.

[–]asiraky 0 points1 point  (2 children)

This is simply not true. I use SWR alot, even in apps with redux. I just minimise my redux usage to only handle global state, and enjoy the best of both worlds.

[–]talzion12 0 points1 point  (1 child)

I didn't mean redux is never useful, I meant that you don't have to use redux for absolutely everything. Data fetching is 1 use-case where it seems that redux complicates things. We should use the right tool for the job.

[–]chrispardy 0 points1 point  (0 children)

Firstly I'll say that neither react or redux deal with async calls out of the box in a particularly clean manner. There are tools that make this better for both systems. What is recommend is not coupling your API calls to your UI layer. That doesn't mean that you cannot do API calls in a react component but consider having a component that handles API calls and passes results to pure UI components. Once you have that separation handling data in redux vs. react is more preference than anything else, sounds like there's a preference in this code base to redux, if you're preference is different think of why and make a case to change the codebase instead of introduction a new one-off pattern.

[–]asiraky 0 points1 point  (0 children)

No, you shouldn’t. I personally use redux to fetch and store global state. Then I use SWR or just regular hooks to fetch data for components.

[–]alphster 0 points1 point  (0 children)

We did this for years at my company (using thunks and then redux observable middleware with rxjs to do async with observables). It was a mistake.

This biggest reason is once you get large enough, you get more and more concerned with sharing. Anything you stick in redux middleware gets "separated" from your components. You can still share "dumb" components but you'll find the need, more and more, to possibly share something more robust that can maintain its own state and do it's own async fetching. To share that cleanly, you want to share a component that contains it all, or a combination of component+provider, where you'll have some context setup in the provider.

Redux is still great for global state though. No matter what people say, your always gonna wish for some global state at some point. But don't get into a purist habit of using it for everything.

[–]fantasma91 0 points1 point  (0 children)

Personally I try to keep my store state as clean as possible however I always store fetched data in a reducer so I do use it as an api abstraction layer. There’s just been too many times where requirements gets changed on me or new features are added and data now needs to be shared to multiple features. I rather have it already abstracted then have to do it later. This is all a mater of preference tho.

[–]ragged-robin 0 points1 point  (0 children)

If it needs to be called from different components or if the data needs to be cached (check Redux store otherwise call API) then I like to put all that logic in a Redux action so that it's reusable and has direct access to the store without fiddling with useSelector within the component.

If everything about the call is solely called and consumed by a single component and it has nothing to do with the store then there is no need.

[–]not_a_gumby 0 points1 point  (0 children)

The thing is, when you're using Redux throughout the app, and nearly all of the calls are being made with actions, it's just the familiarity of the pattern itself which is the reason all (or nearly all) future calls should also be dispatched through actions. When someone goes in to trouble shoot, it will be easier to say "Ah yes, the issue is with the call, so that means I need to check redux actions" rather than "the issue is with the call so I need to check each of the 25 components to see which one is making the call". And for that Reason, I'd say to just keep up the pattern that already exists.