Man, there are loads of stories on medium about Redux being over. It’s really annoying. All it’s going to do is cause web apps to get crappier and crappier.
Hooks Are Fine
I’ve nothing against them, I like them even. Class components in React weren’t great and functional components are way nicer to use. Hooks are fine. They’re not a revolution, they’re just… pretty nice.
My problem is that when you don’t have state management you have to over-use hooks.
Components That Describe The UI
Consider this component:
This component shows you pretty much what the app will look like. Very similarly to html. I know to look in the UI for a bookings table that has tabs and a bulk edit drawer included in it.
In this instance the only logic is a dispatch to tell the app the bookings table has mounted. It’s a lifecycle hook almost like using componentDidUpdate
with a class component.
If I was using Redux for this app bookingsTableMounted
could be picked up in the middleware to run whatever business logic we need to run.
But we can’t do that without Redux, so instead the only place to put it is in the render:
It’s the same component but now it’s juggling more than it needs to.
It’s dealing with the UI, it’s calling to an API, it’s setting the data to local state, it’s even reducing and formatting the fetched data.
Hook Dependencies
Hooks have to account for every-single value and function in their second argument. So the more functions you have to use to format and mutate your data, the more of them you have to include in the array.
None of these functions are ever going to change, so why include them? Mainly because many linter settings you come across will enforce it, but for no logical reason.
I’m uncomfortable with hook dependencies. They’re a bit clunky. The React docs say they’re used internally to decide whether a hook should be re-run or not, which is kind of fine but I still feel like I’m doing React’s maintenance without having much control over the implementation.
For example, you can pass setState
a function, a bit like with hooks, but the function always receives the state as it’s first argument:
this.setState(state => {
// do stuff
})
Why not have something like that in hooks? Why not include the dependencies in the callback argument…
useEffect(props => {
// do stuff
}, [prop1, prop2])
This way I could pass it a function rather than always having to express the callback and rely on closures for its values:
useEffect(doStuff, [prop1, prop2])
Writing Business Logic in Renders is Like Coding While Treading Water
There’s quite a few if
statements and ternaries in my example earlier. All those conditions are trying to manage the fact that the data might or might not exist at any given point in the business logic.
A lot of the issue with using hooks this heavily is that you can’t easily tell when the code is running. Have you ever put a log inside a React component? I mean, you definitely have done that. It runs a ton of times.
The thing about hooks is that often (especially if you’re not using state management) you’ll be passing props to your hooks from other components:
Say users
updates from a parent. That’ll cause any hooks that use it in BookingsTable
to re-run as well, even if that change was irrelevant.
So then another dev might come in and try to fix that by doing another check to users
to make sure the reduce function doesn’t run again unless it needs to.
So more if
statements.
The Great Iffathon
Notice I didn’t even write code to handle any errors that might come back from the API.
First I’ll have to get the error
from the API hook, that means adding it to the massive array and adding another if
:
Then adding it to the render condition (another if
) with a component for that:
Finally, also I might want to reset the local booking state, in case this error happens after that was loaded in the first time.
You got it! More conditions…
The final component is a freaking monster, and it’s not even doing anything that unusual.
I’m Being a Bit Contrived
You could definitely move this logic into a separate hook file, you could also combine the two hooks I’ve made here. Sure, you’ll also probably want to have a <Loading />
and an <BookingError />
component somewhere anyway.
But you still have the problem of treading water wherever you put your hook logic, putting it in a different file just makes it look tidier.
You’ll still have to deal with all the conditions and trying to work out what state each little bit of logic is in or whether it’s running too many times or not because it’ll still be tied to the component render.
And you’ll still be calling hooks with untidy callback expressions everywhere, because that’s the only way you can call them.
All of this nonsense can be avoided and we worked it out ages ago. It’s called middleware.
Middleware Literally Gives You Superpowers
Lots of devs see Redux as a state management library which it is, but that’s not the most useful part of it.
Once you properly get middleware, you’ll use it for almost everything, and you should.
Middleware isn’t redux-thunk it also isn’t redux-saga, they’re middleware libraries. You can add middleware to Redux without using any libraries at all.
It’s just a way for you to do something in-between showing users UI (the bit components are for) and updating the state (the state management bit).
Here’s an example of a custom middleware function…
They’re easy to make.
The guys at Redux have done a great job on making tutorials and style guides that are super useful. Those are free resources that’ll make you a way better programmer because they’re not about just learning a library or a framework, they teach you useful patterns and practices.
If you’re interested, try checking out the Redux section on middleware.