The Simplest React Stack – Part 1: Bootstrapping and State Management
medium.com
medium.com
The goal of this series is not to teach patterns but to promote a simple and flexible stack that you can use with the patterns that you like. (In your case you can move the view specific logic to the level of the components).
It sounds like tracking this by hand ("what is needed where") could get messy, though I don't have experience with this approach.
That's more or less the questions that they were asking in 1995 when they made the design patterns and conceived dependency injection.
I'll first refer to this Twitter thread: https://twitter.com/JEG2/status/902534133427134465
Now, the reasoning is: if you pass everything down, then things get interesting to reason about. Given a state to a component, it will always render the same way (give or take, local state and all). That is only true if you reason about children components as implementation details of the parent. That is, if a parent component renders a child one, and the child needs a prop, the parent should have that prop too.
Otherwise, you could render the parent 6 times with the same props and get 6 times a different result based on some undefined (at the parent level) magical global side effect.
Doing it this way makes your component tree essentially fractal: at any point you could detach an arbitrary component, put it in a library, and use it in another app: there's no assumption. Any part of the app is in itself a small app.
Now, the main issues is the boilerplate of typing it all out (which is minimized if you use a single "model" object at every level and either pass it as a whole, or a smaller part of it, to any child): if you use regular props for every value, you'll have top level components with 100+ props.
The other issue is maintenance. What happens when you move things around. That part really begs for a type system. Use Flow or TypeScript, and just let the compiler bark at you. When the compiler is done barking, you're done fixing, and you have a full statically checked view of your model dependencies. That's awesome!
The rest is mostly cultural. At which point does a developer throw their arm in the air and say "I don't want to do this!". A few months back, Jordan Walke did an Ask Me Anything on Discord where he said he had a pretty high tolerance for passing stuff down manually. I assume that explains some design decisions in React (eg: like how context was not a first class citizen earlier on), but I don't want to put words in his mouth.
For an example of this concept, look at Elm, where that's the primary way to build things. A fully fractal architecture where models come from the top and get passed all the way down to all the turtles. The way you build your model changes a bit (in apps Ive seen, it's more of a mirror of the component hierarchy, and less of a conceptual, normalized model), but boilerplate isn't that bad. Higher than the typical React/Redux dev will tolerate, which itself is higher than what most web devs will...but people who do it generally end up with very high quality apps that are trivial to maintain.
I enjoy the simplicity of it so much that I've now entirely adopted Elm -- type safety, super simple but powerful, and completely based on uni directional data flow with a compiler that is the most helpful I've ever used all wrapped up in the same State -> View function hierarchy that I adore in React.
The library I made supports both of them, but I did not mention local state management for the sake of simplicity. You can find it here: https://github.com/solkimicreb/react-easy-state#easycomp (it will take 1 min to read).
Doesn't it get clumsy to pass state as props to deeply nested comps?
Did you check out mobx?
My guess is that it would be almost as easy and a bit more mature.
MobX is also moving in the Proxy direction with v4, but I do not agree with some of their architectural decisions (mostly the synchronous execution), so I made a separate library. We can discuss these later if you are interested.
In a nutshell: it is used for computed properties, which compute their value on 'get' operations instead of storing them.
function Foo() {
var _bar = 42;
return {
get bar() {
return _bar;
}
}
}
var foo = Foo();
foo.bar
/*
42
*/
However the fact that you can transparently replace a simple property by a computed one (e.g. for backwards compatibility or to trigger side effects in observables) does make explicit, Java-style, getters and setters less useful.