Implementing a Custom classnames Utility in JavaScript



The classnames library in React is a popular utility for conditionally joining class names together. It's especially useful for dynamically applying classes based on state or props in your components. Implementing a custom version of this library will help you understand how to handle conditional logic and arrays in JavaScript.

Let’s walk through how to create a custom version of the classnames utility.


What is classnames?

The classnames utility in React is used to conditionally combine class names. It can take various arguments, such as strings, objects, and arrays, and intelligently join them into a single class name string based on the truthiness of each argument.

Real Interview Insights

Interviewers might ask you to:

  • Implement a function that mimics the behavior of the popular classnames utility.
  • Ensure that the function can handle different types of arguments, such as strings, arrays, and objects.
  • Handle edge cases like null, undefined, or non-string values.

Implementing customClassnames Function

Here’s how you can implement a custom classnames function:

function customClassnames(...args) {
  const classes = [];
 
  args.forEach(arg => {
    if (!arg) return;
 
    if (typeof arg === 'string' || typeof arg === 'number') {
      classes.push(arg);
    } else if (Array.isArray(arg)) {
      classes.push(customClassnames(...arg));
    } else if (typeof arg === 'object') {
      for (const key in arg) {
        if (arg[key]) {
          classes.push(key);
        }
      }
    }
  });
 
  return classes.join(' ');
}
Explanation:
  • String and Number Arguments: If the argument is a string or a number, it is directly added to the classes array.
  • Array Arguments: If the argument is an array, the function recursively calls customClassnames on the elements of the array, which are then joined into the final string.
  • Object Arguments: If the argument is an object, each key is treated as a class name, and it is added to the classes array only if its corresponding value is truthy.
  • Falsy Values: If the argument is falsy (like null, undefined, false, 0, or an empty string), it is ignored.

Practical Examples

Let's see the customClassnames function in action:

// Simple string and number arguments
console.log(customClassnames('btn', 'btn-primary')); // Output: "btn btn-primary"
 
// Conditional class names with objects
console.log(customClassnames('btn', { 'btn-primary': true, 'btn-disabled': false })); 
// Output: "btn btn-primary"
 
// Handling arrays of class names
console.log(customClassnames(['btn', 'btn-primary'], 'active'));
// Output: "btn btn-primary active"
 
// Combining multiple types of arguments
console.log(customClassnames('btn', ['btn-primary', { 'btn-large': true }], { 'btn-disabled': false }));
// Output: "btn btn-primary btn-large"

Handling Edge Cases

  1. Falsy Arguments: Ensure that null, undefined, false, 0, and empty strings are ignored and do not add unnecessary spaces in the output.
  2. Nested Arrays: The function should handle nested arrays gracefully, flattening them into a single list of class names.
  3. Non-String/Number Keys in Objects: While keys in objects should generally be strings, ensure that your function gracefully handles cases where they are numbers or other types.

Use Cases for classnames

  1. Conditional Class Names: Apply classes based on component state or props in a clean and readable way.
  2. Dynamic Styling: Easily toggle styles based on user interactions or other dynamic conditions.
  3. Reusability: Make component styling more modular and maintainable by avoiding repetitive conditional logic in JSX.