It seems like a good trade off—yes, everything must be done just so, and perhaps some parts of your app are a little awkward, or a little boilerplate-y, but there’s a whole giant class of problems you can just totally ignore.
Until... you can’t.
There’s another approach to library authorship, where the goal is not to delete a class of problems, but to make a class of problems easily controllable. Not to make a large domain disappear, but to make it programmable.
I have found, with that as your goal, you will be pushed away from declarative models. Because declarative models are about making a surface which is not programmable but configurable.
You can’t program arbitrary relationships across a React component boundary. You must flatten your intent into the very tight declarative interface which exists there.
Procedural models allow you to use the full set of capabilities that a function call and type system gives you. Ideally you don’t use much of that capability at any one time. But the beauty is you can use exactly the right control structure for the specific concern you are trying to... not abstract away, but make controllable.
The set of “concerns” is the infinite set. And no possible declarative model can capture it. Functions can.
SQL -> SQL procedures
Angular templates -> DOM manipulation
Puppet -> custom scripts
For example, HTML/SVG/CSS DOM scripting can be considered "procedural", but the in other sense the DOM is a rather declarative version of rendering, whose procedural escape hatch is Canvas/WebGL.
You may even find case where they are combined tightly:
A makefile has declarative dependencies and procedural recipes.
But once you are out the escape hatch you will need to model both the new space you are working in, AND model the world you escaped from in order to interoperate, which is a special hell.
The idea is always to put a layer on top that simplifies things, so it's easier to use and faster to learn. But the end result is usually you have to learn both the abstraction and thing it was trying to hide.
If functions are your declarative model, what would you say?
So when I talk about declarative models I’m talking about the Swiss cheese of declarative models with procedures mixed in.
I don't need the freedom to invent my own character encoding, or the freedom to create SQL injection vulnerabilities, buffer overflows or use after free bugs. That's just something that drags me down when I'm writing a nice web app to have fun with friends or a good old enterprisey web app at work. I need the freedom to create working applications quickly.
Flutter gets it right. The code looks almost like it is declarative. But really it's just Dart code (an underappreciated language IMO) and you never get stuck by things that are impossible to express.
I need to figure out how to express this better in the future, but if you swap "Dart" for "Rust," this sentence applies to moxie as well. It's what I mean when I say that the callgraph of functions is used for structure.