Look like the button example is a good enough convention for me, I don't need any of the other conventions. Can anyone tell me why this is not sufficient?:
const classNames = (pojo, c=[]) => {
for (let k in pojo) if (pojo[k]) c.push(k);
return !!c.length && c.join(" ");
}
Do I need array-flattening support, multiple argument support? The size of this library probably comes from it supporting a handful of different coding styles. I would say the size (50 lines) is too large, if anything. Dependency worthy? I'm not convinced. I am very much so against requiring trivial utility functions from other people. Roll your own if it's trivial! It's better than depending on something over the network.
I like that the component's main class stands out and is easy to find, that allowing the consumer to pass along their own class is trivial, and that the dynamic classes are grouped together.
So I'd argue multi argument and falsy thinning are welcome features. I've never used array flattening.
Does it really need to be a dependency? I guess that's a matter of opinion. I like that the various React projects I deal with on a regular basis all handle classes in a consistent manner. I also don't usually mind dependencies that don't pull the entire world in with them.
This is a matter of convention. Another, equally valid convention:
classNames({
mainClass: true, // first key is main class
[props.className]: true, // derived
otherClass: true, // rest
yetAnotherClass: true // ...
})
At the end of the day, everyone has to learn their codebase's convention, and that's all that really matters. One convention isn't better or worse than any other, but it's best to have one and stick with it. I don't need a library which supports 10 different conventions if my chosen convention only requires 2 lines of code. Your convention could be implemented in a few lines of code, as well, and then you'd be forced to stick with it, which is probably better. Using this library, 5 different developers could be using 5 different conventions. The helper function I suggest forces a single convention, which lowers complexity.
The props.className one in particular is pretty garish. Probably should do !!props.className to help explain intent a bit, which adds to the tedium. Once you've added falsy thinning and arbitrary arguments, you're 99% of the way to classnames.
Roll your own, untested function vs a small, widely used library with a well-understood, well-tested API that your devs have a good chance of being familiar with already. I too want to avoid bloated npm dependencies but this seems like a fair engineering trade-off..
This is a trivial function, your argument applies to complex utilities. It is piss easy to roll your own, and it's not worth introducing a potential attack vector via a new dependency. In this case, if your devs can't keep up with your classNames function, fire them, because it's trivial.
it("should work with no classes", () => {
expect(classNames({}).to.be.false
})
it("should work with a single class", () => {
expect(classNames({a:true})).to.equal("a")
})
it("should work with booleans", () => {
expect(classNames({a:true, b: false}).to.equal("a b")
})
it("should work with falsy and truthy values", () => {
expect(classNames({a: 5, b: ""}).to.equal("a b")
})
This isn't redux. It's a trivial utility function. Your kind of thinking is too often applied without thinking. Before depending on someone's code, ask yourself if you can roll your own in under 5 minutes. The answer could easily be yes. There is no good reason to depend on someone else's code if it's trivial. Having more dependencies than you need is a cardinal sin that way too many javascript developers commit.
If you write 100 ~50 line "trivial functions" you've now written 5000 lines of code that doesn't actually solve your underlying problem, it just makes it slightly easier.
And you can't write this particular utility function in 5 minutes and support all the uses-cases, particularly if you are going to write a bunch of unit tests.
The reason to depend on someone else's code in trivial use-cases is entirely because they are trivial. There's nothing you're doing aside from wasting time by writing it. lodash is literally a massive library of mostly "trival" functions that no one is every going to write themselves because WHY waste the time?
The only cardinal sin at play here is thinking that wasting your time reinventing the wheel is time well spent. This approach is what I'd expect from either a very young developer or a very self-centered one.
No, we're talking about 2-3 line functions. Stop arguing with a straw man. You're taking what you know to be true about complex code, and then asserting that those things are also true with simple code. That doesn't work.
> And you can't write this particular utility function in 5 minutes and support all the uses-cases
The beauty is that because we are rolling our own function, we only need to support the single function signature we decide we're going to stick to in our codebase. Supporting every use-case becomes a moot point.
The author of the library has to support the 10^5 people who use his code, meaning he's more likely to merge silly pull requests like "should take objects", "should flatten the input array", "should allow multiple arguments", "should work with X", "should do Y" because people have different styles.
> The reason to depend on someone else's code in trivial use-cases is entirely because they are trivial. There's nothing you're doing aside from wasting time by writing it
It only takes time if you are not able to do basic programming. I find basic programming to be a much more enjoyable and productive approach than depending on potentially dozens of trivial libraries whose APIs are not the way I would design them, and could change at any time. Learning dozens of APIs takes just as much time as implementing them for a certain set of functions. That set is different for everyone, but it is not the null set for anyone.
> No, we're talking about 2-3 line functions. Stop arguing with a straw man. You're taking what you know to be true about complex code, and then asserting that those things are also true with simple code. That doesn't work.
The library in question is literally 50 lines of code. About 30 when you remove the exports and comments at the top. I find it somewhat ironic that you are so opposed to using utility libraries and you didn't even bother to look at the one in play here.
> The beauty is that because we are rolling our own function, we only need to support the single function signature we decide we're going to stick to in our codebase. Supporting every use-case becomes a moot point.
No it doesn't. All the use-cases are useful. You use strings when you need default classes and objects when you have conditional classes. You can't implement both of those in two lines of code.
> The author of the library has to support the 10^5 people who use his code, meaning he's more likely to merge silly pull requests like "should take objects",
It's always taken objects as properties and for good reason: it's a clean way to separate conditional classes from unconditional ones.
But sure dude, all those features are insane. Put his code up to a solid 30 lines. Really saving the bacon by rolling your own that doesn't do the same thing.
> Learning dozens of APIs takes just as much time as implementing them for a certain set of functions. That set is different for everyone, but it is not the null set for anyone.
Yeah, that's why everyone writes their own lodash. It's just a set of trivial functions.
Yeah and if you've analysed 100 sets of docs for 100 dependencies you've done a ton more work than writing 100 trivial functions.
Why does everyone in JS land treat dependencies like they're just some free magic, as if an intimate familiarity with the source code is injected into your entire team's brains the moment you type 'npm install'? I've worked with far too many people that blow out the package.json file for every little thing then wonder how every other dev on their team seems to be paralysed by the smallest task.
Every time someone on your team runs into a dependency boundary they need to stop reading code, pull up a browser, and start reading docs. You can't trace the path of execution through a black box dependency, which means if you use a bunch of them in one place your developers are going to have to hold a hell of a lot of information in their head while they analyse that code. If they drop something, it's back to the docs again. At the extreme, it's a context switching nightmare.
Dependencies cost time, effort and brain power. Make sure you're getting enough in return.
You're absolutely wrong. If reading the docs were harder/more work than writing the code we'd have 100s of implementations of lodash. Instead people use lodash.
Dependency boundary? If you can't figure out what a function called classNames does in the context of a React render function in which the output of classNames is put as a value in to "className" attributes, you need to find a new job.
Dependencies cost time, effort and brain power when they actually require those things. Be thoughtful when using something that actually requires investigation. But honestly, a trivial function should be self-documenting or it really isn't trivial, is it? If I had an npm library called array-flatten are you seriously going to read the docs, or assume it flattens nested arrays?
I never said anything about writing the dependency. I'm talking about reading and writing code that uses it.
Of course it's easier to read a trivial function than the docs for a trivial function. That's what makes it trivial, it's not abstracting away anything meaningful. And of course I can easily figure out what classNames outputs.
You know what I can't figure out trivially? What inputs it takes. It's a polymorphic function. Me and Bob load up a new codebase, I open a few files and see classNames used a couple of times with an object parameter. I can now be pretty confident what the API of the function is. Bob opens up a couple of different files and sees it used with an array parameter a few times. Now he's pretty confident he's got the hang of it too. We both go off to write new components. I've got a bunch of this everywhere:
{ foo: true, bar: true, baz: true }
And Bob has a bunch of this everywhere:
[foo ? 'bar' : null, baz ? 'boop' : null]
Bam, inconsistent code. Could have easily been solved by reading the docs before using something though. And hey, at least we'll both remember for next time...
...Unless our team imports 100 of these libraries and tries to enforce a consistent use of them across the project.
There's only so much API surface area one dev can keep at the front of their mind while coding. Good devs understand this and minimize it. Bad devs pile in trivial dependencies because they don't understand their cost.
Oh, and how do I know whether flattenArray is recursive or not? What if it is and I only want to flatten one level? Does it take a second integer parameter? Or an options object? Since we're talking about JS, does it flatten Array-like objects? Is that the same API or a different one? It's almost like your example proves my point...
Yeah, and in CR you should be told not to use the object form when you have "true" as the value, as it's pointless. That's what string form is for. The "docs" are literally 5 lines where you learn you can send object literals, arrays and strings.
> There's only so much API surface area one dev can keep at the front of their mind while coding. Good devs understand this and minimize it. Bad devs pile in trivial dependencies because they don't understand their cost.
Yes, because if you implement a similar feature the remaining members of your team all know it through osmosis. The surface area of these functions is tiny, but apparently your implementation requires no investigation by anyone.
> Oh, and how do I know whether flattenArray is recursive or not?
Holy cow, man. Do you literally hand-implement EVERY utility function you've ever needed?
> It's almost like your example proves my point...
Your point seems to be: you can't manage to infer ANYTHING from a function signature AND that apparently somehow your team members can ALWAYS infer everything needed from any function YOU write.
Use code inspection and you'll never open the docs for flattenArray or anything like it again. If this is the amount of effort required for flattenArray for you, how to you actually use large libraries with HUGE surface areas?
> Yeah, and in CR you should be told not to use the object form when you have "true" as the value, as it's pointless. That's what string form is for. The "docs" are literally 5 lines where you learn you can send object literals, arrays and strings.
What a dumbass argument. So instead of writing a one line abstraction, I should invest a bunch of CR time explaining to my whole team which of 3 different APIs they should be using in this dependency I've brought in?
> Holy cow, man. Do you literally hand-implement EVERY utility function you've ever needed?
No, just the trivial ones. Have you been actually reading this thread?
> Your point seems to be: you can't manage to infer ANYTHING from a function signature
No, my point is that you can't infer the full API of a function from the arguments passed to it's invokation when the function is polymorphic.
> AND that apparently somehow your team members can ALWAYS infer everything needed from any function YOU write.
If the function is trivial, which is exactly what we've been talking about this whole time, then No. Fucking. Shit. The function is trivial and they have the source code right there.
> So instead of writing a one line abstraction, I should invest a bunch of CR time explaining to my whole team which of 3 different APIs they should be using in this dependency I've brought in?
It's not a one-liner because it doesn't support the same feature set. It supports a basic use case that may be great for trivial apps, but in the real world it isn't.
> No, just the trivial ones. Have you been actually reading this thread?
And said trivial ones must still be "learned" by your co-workers. You've saved exactly zero time.
> No, my point is that you can't infer the full API of a function from the arguments passed to it's invokation when the function is polymorphic.
You can with a glorified text-editor these days with Code Inspection. I guess that's advanced stuff we shouldn't expect our co-workers to use?
> If the function is trivial, which is exactly what we've been talking about this whole time, then No. Fucking. Shit. The function is trivial and they have the source code right there.
Yet, if the function is trivial AND is imported via npm, it suddenly requires reading docs. On one hand you argue "triviality requires reading docs" and then suggest that you can implement a similar trivial function and everyone can just look at the source code.
You're conflating a trivial function with a trivial API. The whole reason I jumped into this thread is because classnames is a great example of a trivial function with a non-trivial API. Yes, you do need to look up either the docs or the implementation to understand how to use it. I think my past examples made that clear.
What I'm saying is that it's EASIER to understand the source code than it is to understand the documentation for classnames. That's because it abstracts nothing away. It's replacing one simple API (template strings) with another one that's very similar and of roughly the same power. By turning it into a dependency you're adding an artificial barrier between it and your code base, that makes it harder for everyone to grok. Not much harder, but enough to be a pain in the ass in any codebase where that same tradeoff is made over and over.
If you can't understand that, then I don't know what to do for you. Keep writing shit JS code and making the web worse than it already is.
My test would fail, and I would fix it. + 2 seconds. Actually, the next test has the same problem. 2 more seconds. I'm gonna be in trouble with my boss!
No, your test wouldn't fail because you wrote your code already being confident it is correct and went on. The bug would surface later in your team, somebody else in your team not familiar with your code would spent hours figuring out what is wrong, will consult you about your intentions when you wrote the code etc.
No, the test would fail in this case because the code was written properly because it was a TRIVIAL 2-line function (see above).
A few people in this thread have regurgitated the same reasoning you have. The problem is that your reasoning is NOT an invariant under complexity. This reasoning does not apply nearly as much to trivial utility functions such as:
const isObj = o => !!o && typeof o === "object"
and classNames, etc. as it does to larger, more complex code.
> will consult you about your intentions when you wrote the code etc.
No, no they won't. No one will consult me on what my 2-line classNames functions does. It's 2 non-obfuscated lines. These aren't undergrads in CS101.