When I first started learning React, I had some initial confusion. In fact, I think that almost anyone who's done React wrestles with the same que...
For further actions, you may consider blocking this person and/or reporting abuse
Hey Adam,
First off, I just want to say thanks for the article! I don't agree with your proposal, but it was a really interesting thought experiment to Rey to figure out why not. Redux can definitely feel like a lot of overhead, and pursuing alternatives to is awesome.
Second, if you're up for it, I'd love to hear your thoughts on my points below.
Okay, so that said, I can offer at least 3 concrete reasons why I don't believe this proposal is not scalable, mostly related to maintainability. These are my own thoughts, mostly gathered from experience.
1) You've created an implicit dependency on your application hierarchy being instantiated in a specific order. While this works great for small projects where you are the only dev, it tanks the maintainability because it requires anyone who works on it to have knowledge of that implicit dependency. This increases onboarding time, makes it harder to debug, and makes your application more brittle with regards to change.
2) The singleton component cache will exist before, and outlive, all of your components. This means that, when reasoning about your component, you now have to consider not only what the state of that cache is now, but also what it was like in the past. Again, probably fine for a small application, but this can quickly lead to very hard-to-trace bugs in a large application. This also results in components that are way harder to test, because you now have to manually tear down and set up the component cache before every test.
3) The singleton only holds a few components right now, but when you have even half a dozen devs working on a mid-sized application, the potential for naming collisions starts to increase pretty drastically. Now you have state that out lives your components and is directly changed by components from totally different contexts.
Some additional, more fluffy points:
You've created dependencies both up and down the hierarchy. This means that, if you want to change something, you now need to tear out twice the dependencies.
Using a solution that only works for singletons means that you need a second solution for everything else. Now, whenever a new dev opens a component, they first need to figure out which of the two state management systems it uses.
I would at least suggest that, rather than importing a singleton component cache, you pass your cache to child objects. It's just one argument instead of multiple. This allows you to separate contexts, makes testing easier, and at least hints to other devs that there's something extra they need to provide (though I would wager that just using React's context API is probably the best choice).
Thanks for the article! Even if I don't agree with your proposal, it's always cool to try to understand why. As said, I'd love to hear your thoughts.
Regards,
Patrick
I'm gonna circle back to this in a bit (hopefully, later today when I have some more time). But I wanted to drop a quick reply here to say thank you for these thoughts. It's already given me some ideas to chew on. And I've never been bothered by the fundamental idea that my technique might not be "best practice". I've been bothered by the fact that those who dismiss it have only bothered to reply with dogma and with vague notions of "industry standards" - without being able to actually say why something is-or-is-not an "industry standard".
No worries!
Thanks for letting me know - I always worry when posting these kind of responses that they're not going to land right.
Also, I totally agree with your stance on dogma - if you're using something simply because it's really popular, without understanding the pros and cons, you might as well be blindly copying code from Stack Overflow (actually, I think that might be the less harmful of the two).
OK, now that I've had some time to work through some coding samples and figure some things out, I finally have time to put a "proper" reply on your well-considered points. And... I'm not going to.
Because the stuff I've figured out over the last 24 hours (with the awesome feedback of you and others on this post) really shouldn't be encapsulated in a single comment/reply. I'm gonna put this into a new blog posting.
But if you're curious about the Cliff's Notes version of what I've come up with:
That's what I'm gonna explain in my next post...
Wow, thanks! I'm stoked you found my ramblings useful!
Your article was really thought-provoking. Also, respect for posting things you know people disagree with - that stuff terrifies me. Have fun with the Context API! I'm looking forward to your next post!
The follow-up...
dev.to/bytebodger/throw-out-your-r...
Haha.. this is one of the best articles I've read in quite some time. It is a cool solution. As you said, it is not suitable in all cases but I can totally see myself using it under the right circumstances. I am also bothered by the dogmatic aspect of the community... Try to criticize React Hooks and see what happens ;)
Oh, man... Don't even get me started on Hooks. I'm sure I'll do another post on that one some day. Hooks are fine. Hooks are cool. But almost since the day they were announced, I've noticed that it inspires some kinda messianic fanboy complex in those who love them. It seems that they're incapable of seeing/using Hooks as a tool to be leveraged when appropriate. Instead, they start marching around and singing some hypnotic "Hooks Chant" as though merely saying the word "Hooks" will make all of their applications magical and bug-proof.
The context API exists as well and can be a solution :
reactjs.org/docs/context.html
Mind you, it doesn't seem suitable for high-frequency updates :
github.com/facebook/react/issues/1...
(but maybe it has been improved since?)
I agree that this "issue" is really what the Context API was designed for. Every time I've tried to dive in and use it myself, it just doesn't seem to "grok" properly in my brain. But that's not a React problem or a Context API problem. That's a me problem, and I just have to endeavor to make myself more comfortable with it so I can truly assess its faults-and-benefits.
Also... if you look under the covers, Redux basically is the Context API. That's what it's using.
Finally, I like how you point out the potential similarities with Redux (umm... sorta) and MobX (much closer). I'll freely admit that, in a "corporate" coding environment, if given the choice between using my custom technique or using MobX, I'd probably just use MobX. My technique and MobX both allow for a shared reference to functions (as opposed to serializable data).
But I wouldn't want to move the functions out of their original components. IMHO, if a component does "a thing", then I want to see all the functions that are specifically used to do "that thing" right there, in that component. But I freely admit that this bias is more of my own personal coding preference.
There is a practical reason for keeping the functions with their original components, however. That reason is: state management/updates. I've found that this technique can wander into bizarre territory if I'm trying to
setState()
in a given component - but the function that accomplishes thesetState()
is not in its original component.As for functional components, I freely admit that, in my "side" dev, I've still been writing a lotta "old school" class components. And this solution was built with that in mind. But I'm not entirely understanding why it simply "wouldn't work" with functional components? Granted, I'm not trying this in an IDE right now, but isn't the following valid?
Of course, you'd have to ensure that your app is designed in such a way that
DoSomething()
can never be invoked until after the<ContentArea>
component's been mounted.The answer still holds true, though. Simply because it's not the way React works.
That's not a bad thing, mind you. Just because it doesn't work that way, doesn't mean it couldn't work that way. That's how most things come to be. We use product A, B and C. Honestly, we just want a product that does some of the things from A, some of the things from B and some of the things from C combined. Now we've got the iPhone.
I see the answer as a very strong argument, which I'm willing to break down. It doesn't work that way, but I'll make it work that way! Then if it does work that way, and it works well, you can go back to the source and let it adopt the changes.
I think it depends on how literal we're being about "it's not the way React works". I agree with you that, if the app/library/tool/whatever truly doesn't work that way, then it can absolutely be a fun and enriching challenge to figure out how it could work that way. But I feel the issue is that, often, when people say, "That's just not the way that this works" they're kinda fibbing about that. Many times, the technique does work that way - but the person claiming that it "doesn't work" is really saying, "This doesn't work with the way that I personally want to see code written." In other words, when some people utter those words, they're not referencing empirical truth. They're just using "it's not the way React works" as a cover for their own preferences - and their own dogma.
As for scaling, this is a great point. In my experience, most of the "true" Single Page Applications don't get toooo big. At some point, you end up shunting the user to another application (even if it's designed to feel, to the user, like it's all the same application). I'm currently using this approach in an app that has 35k+ LoC, but I understand that a single SPA could, in theory, get far larger than that and the references could get tricky.
As to your solution, I totally agree on the potential to create a proxy object that would have at least some additional control as opposed to a plain ol' JS object. In fact, I know that I've thought about that idea before. I may even tinker with something like that in my free time today...
As for race conditions, I completely agree with you. That's why I have a particular application structure in mind when I feel like using this approach. Most of the time, I have some component that sits near/at the top of the hierarchy. Then I have descendants, nested much lower on the tree, that leverage the
components
object to access things that were higher in the tree. In this scenario, it's impossible for the descendant to be mounted before the ancestor, so it eliminates most of the race-condition concerns.Granted, I didn't spell this out in the original post, but if I have sibling branches in that tree, I don't try to use this approach to "talk" between one sibling branch and the next. Instead, I use it to "talk" up (or, occasionally down) a given branch. But I can understand where this is a potential pitfall of my approach, because this "don't talk between branches" rule is something that's only been enforced in my head - and it's not in any way enforced by the technique itself.
I actually have this in my list as an item to blog about. The topic I'm referencing is the idea (that I've witnessed lately) amongst many React devs that we need to religiously separate our business logic and our display logic. The only issue I take with that is that, in most React applications, almost all the logic is display logic. And thus, IMHO, much of the logic actually belongs in the function/component where it's being rendered. Of course, there are exceptions to this. And I'm not saying that there's not a time-and-place to break out some of the business logic into separate components/functions/layers. But there are far too many people holding onto the old MVC edicts who think that damn near any "logic" should be washed out of React components. I... have a more nuanced opinion on such things.
Nice article, but man.. I don't carry all my arguments with me all the time for every decision that I make, you know :) Not to mention that if you asked me on the spot, I'd have 10 other things that I'd be thinking about right there and then. Plus, when I'm working, I'm not consciously trying to recite every argument for every decision that I make. I "just know". It's intuitively based on hundreds of previous decisions that I've made. Think about that :)
This misses the whole point. Anyone who's been doing this for long enough has a stockpile of "rules of thumb" they use to make snap coding decisions. And they rarely sit there and "recite every argument for every decision" they make. I get that. No one disputes that.
But that was never the point of this article. The real question is: "When someone else is coding something in a way that you don't personally like, what reason do you give to explain why you don't like it, and why they should possibly feel compelled to change it?"
Do you just throw up some baseless bromide like, "Well, umm... it's, uhhh... It's an antipattern!" And then just cross your arms and smile, content in the idea that you've somehow quantified your objection? Or do you try to give the person tangible, empirical, logical reasons for why their approach might not be ideal??
If you think that someone else's coding is indeed an antipattern, that's fine. But if your sole explanation of the "problem" is to tell them that it's an antipattern, then, well... that's kinda messed up.
The specific practice I outlined in this article is a great example of this. Even with all of the great comments left on the article, I've yet to have anyone give me any tangible reason why this approach may be an "antipattern".
To be clear, some people have pointed out some downsides to this approach. But that doesn't make it an antipattern.
Too often in our career field, one dev tries to shout down another by hollering that something is a "code smell" or an "antipattern" - when they can't give you any specific reasons why the approach is supposedly suboptimal.
I don't necessarily disagree, it really depends who is asking the question and who is answering. I definitely respect your passion for the craft, though. You obviously have the need to understand well what you do.
And about the other comments, for me, they are more than enough to call it an anti-pattern and leave it at that. But again it depends who that is coming from.
I've got colleagues whom I'd definitely challenge when they say something is an anti-pattern. I also know people who if asked the same question and say it's an anti-pattern, I'd stop there and start questioning myself instead of expecting them to answer to me.
So it's very contextual I'd say at the very least.
Ahh, I see what you're getting at. And yeah, that's why I wrote my example outside the function, because you can't register it at runtime.
Wow. This is a great response. And exactly the kinda feedback I was hoping for. So I'm gonna break up my comments in a series of replies...
I wouldn't be so dismissive and quick to generalise. I know it's easy but not sure it is wise (it is what we call "junior speak").
If you want to use Redux, hooks and everything else that becomes trendy in all cases without consideration, go for it.
You might be interested in the method registry I've created: github.com/dexygen/withMethods
Fair enough