jahed.dev

Moving off Recompose: The Final Mile

A few years back as React's Functional Components were gaining widespread usage as an alternative to Class Components, there was a popular library called Recompose. You see, while Functional Components let you creating React Components without the Class boilerplate (extends, constructor, this, etc.), it didn't let you store any state, handle lifecycle events (mounting, unmounting, etc.) and of course there was no dirty inheritance. Like with most functional programming, the solution was composition.

To store state, the easiest way was to create a Class Component and imagine it doesn't exist by wrapping it in a function that could be composed like any other. These functions which create components were called Higher-Order Components (HoCs). Recompose was a collection of HoCs that created these Class Components for you so you didn't have to deal with Class Components at all. Pretty useful.

End of Recompose

Over time, as Functional Components became more and more popular, React introduced Hooks. A way to "hook" into React's lifecycle through Functional Components to store state and trigger side effects. So the maintainer of Recompose decided it's no longer worth maintaining and suggested everyone move over to hooks. Of course, migrating from HoCs to Hooks can be a lot of work when done in bulk.

Endless Migration

My project, FrontierNav, used it quite a lot, in places that I'd rather not touch. But of course, code rot is a thing, and I knew I'd have to move off recompose sooner or later for my own benefit. So I took the approach of migrating a handful of components as a sort of warm-up every few weeks. Refactoring is a great way to enter flow state so these sorts of repetitive and almost automatic migrations are a great way to trigger it.

Over the year the code rot started to show. Recompose uses createFactory, something React decided to deprecate. Every time in development, React will print a warning in the console asking me to stop using it before it's too late. Of course, Recompose wasn't going to remove it, so I just had to ignore it. Ignore the rot, until my gradual migration was complete.

There came a point where the warning wasn't going away and there was one usage left. A complicated one that regret ever writing. It remained there for months. Nagging me.

The Final Mile

With HoCs, it's very easy to get into a functional programming state where you don't realise the true complexity of what you're writing. It's just layers on layers and it looks so simple. This particular HoC abomination was conditionally subscribing to and fetching multiple nested pieces of documents asynchronously from Firebase and rendering different components based on what it can and cannot find.

Since Hooks require being executed in the same order on every render within a component, they are terrible for this sort of conditional logic. One solution is to move all of that logic out into a regular function and resolve it into a single actionable result for the hook to handle. But hang on a minute.

Firebase... Again.

The thing is, such a solution would still be complicated, and it'd be a one-off. No other part of FrontierNav needs this (yet). The core problem is that Firebase doesn't let you write complex queries (joins). You need to subscribe to individual pieces of documents manually. The last thing I want to do is write a library or on-board an existing one that solves this problem and ties me further to Google's proprietary platform.

No, I'd rather rewrite the last few Recompose functions I'm using to get the job done and be done with it. And 100 lines later, that's what I did. Now I just need to avoid looking at it.

One day, I'll have some sort of plan to move off Firebase. Currently I don't want to commit to an alternative centralised solution. I'd much rather wait until I've realised a more decentralised solution where people can own their own data and I'm not responsible for any of it.

Thanks for reading.