Still the same conversation with my friend who recently went for an interview. After the Python indentation task, the next challenge was another familiar one for front end engineers — implement the classnames
utility, or the newer clsx
used in React apps.
If you have used React, you have probably written code like this:
<div className={classNames('btn', isActive && 'active')} />
This utility helps combine class names conditionally and neatly, now let’s see how simple it is to implement it.
classnames is a commonly-used utility in modern front end applications to conditionally join CSS class names together. If you’ve written React applications, you likely have used a similar library.
Implement the classnames function.
Examples
classNames('foo', 'bar'); // 'foo bar'
classNames('foo', { bar: true }); // 'foo bar'
classNames({ 'foo-bar': true }); // 'foo-bar'
classNames({ 'foo-bar': false }); // ''
classNames({ foo: true }, { bar: true }); // 'foo bar'
classNames({ foo: true, bar: true }); // 'foo bar'
classNames({ foo: true, bar: false, qux: true }); // 'foo qux'
Arrays will be recursively flattened as per the rules above.
classNames('a', ['b', { c: true, d: false }]); // 'a b c'
Values can be mixed.
classNames(
'foo',
{
bar: true,
duck: false,
},
'baz',
{ quux: true },
); // 'foo bar baz quux'
Falsey values are ignored.
classNames(null, false, 'bar', undefined, { baz: null }, ''); // 'bar'
In addition, the returned string should not have any leading or trailing whitespace.
I have solved similar problems before, like converting snake_case
to camelCase
, which taught me to check the type first — if the value is a string, object, or array — and then deal with each case properly.
This problem is about combining class names based on rules. The tricky part is dealing with mixed types, recursive arrays, and ignoring falsey values like null
, undefined
, false
, and ''
.
I broke the problem into smaller pieces:
Use the string as-is (after trimming).
if (typeof input === 'string') {
return input.trim();
}
Just convert it to string.
if (typeof input === 'number') {
return input.toString();
}
Recursively flatten each value inside the array.
if (Array.isArray(input)) {
return input.map(flattenClass).join(' ');
}
Pick only the keys that have truthy values.
if (typeof input === 'object') {
return Object.entries(input)
.filter(([_, value]) => Boolean(value))
.map(([key]) => key)
.join(' ');
}
Ignore everything else (null
, false
, undefined
, empty string). After applying all these steps, collect the results and join with a space.
export type ClassValue =
| string
| number
| boolean
| null
| undefined
| ClassDictionary
| ClassArray;
export type ClassDictionary = Record<string, any>;
export type ClassArray = Array<ClassValue>;
export default function classNames(...args: ClassValue[]): string {
return args.map(flattenClass).filter(Boolean).join(' ').trim();
}
function flattenClass(input: ClassValue): string {
if (!input) return '';
if (typeof input === 'string') return input.trim();
if (typeof input === 'number') return input.toString();
if (Array.isArray(input)) return input.map(flattenClass).join(' ');
if (typeof input === 'object') {
return Object.entries(input)
.filter(([_, value]) => Boolean(value))
.map(([key]) => key)
.join(' ');
}
return '';
}
You can practice this question here