Implement classNames
Rebuild the `classnames`/`clsx` utility that every React app leans on — join a messy mix of strings, condition objects, and nested arrays into one clean class string. The whole trick is deciding what each argument *is* and recursing when it's a container.
The problem
Write `classNames(...args)` that joins its arguments into a space-separated class string. A string passes straight through; an object contributes each key whose value is truthy (`{ foo: true, bar: false }` → `'foo'`); and arrays are flattened recursively so `['a', { b: true }]` behaves like the same values passed at the top level.
Two edge rules do the real work. Every falsy argument — `null`, `undefined`, `false`, `''`, `0` — is ignored entirely, and the returned string must have no leading, trailing, or doubled whitespace. That means falsy values can never reach the final `join`, or they'd leave stray gaps.
The approach
Walk the arguments and branch on type. Skip anything falsy up front with `if (!arg) continue` — that single guard handles every ignore-me case in one line and, as a bonus, filters out `null` before the `typeof` check (since `typeof null === 'object'` would otherwise misroute it into the object branch).
For arrays, recurse with `classNames(...arg)` and push the result only if it's non-empty — that's what makes nested objects and deeper arrays get the exact same treatment as top-level ones. For plain objects, loop the keys and push each one whose value is truthy. Everything else is a string or number, so push it directly.
Collecting into an array and joining with a single space at the end is what keeps the whitespace clean: because falsy values are dropped before they're collected, `result.join(' ')` never has an empty slot to pad, so no trailing or double spaces can sneak in.
The solution
/**
* @param {...(any|Object|Array<any|Object|Array>)} args
* @return {string}
*/
export default function classNames(...args) {
const result = [];
for (const arg of args) {
if (!arg) continue; // ignore falsy: null, undefined, false, '', 0, NaN
if (Array.isArray(arg)) {
const inner = classNames(...arg); // recurse, flatten any depth
if (inner) result.push(inner);
} else if (typeof arg === "object") {
for (const key in arg) {
if (arg[key]) result.push(key);
}
} else {
result.push(arg); // string or number
}
}
return result.join(" ");
}Time O(n)Space O(n)