Code Review React Code Snippets Sequels - herougo/SoftwareEngineerKnowledgeRepository GitHub Wiki

Follow-ups to React Code Snippets

4.1: Dedicated Class to Organize Tailwind Classes

import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';

export type NestedObject<T> = {
    [k: string]: T | NestedObject<T>
}

const concatClassNames = (
    classNames1: string | undefined,
    classNames2: string | undefined
): string | undefined => {
    if (!classNames1) {
        return classNames2;
    } else if (!classNames2) {
        return classNames1;
    } else {
        return `${classNames1} ${classNames2}`;
    }
};
  
function flatten<T>(obj: NestedObject<T>, parentKey = '', result: Record<string, T> = {}): Record<string, T> {
    for (const key in obj) {
        const value = obj[key];
        const newKey = parentKey ? `${parentKey}_${key}` : key;

        if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
            flatten(value as NestedObject<T>, newKey, result);
        } else {
            result[newKey] = value as T;
        }
    }
    return result;
}

class TailwindClasses<T> {
    constantClassName: string;
    dynamicClassNameFn: (undefined | ((args: T) => string));

    constructor(
        constant: NestedObject<string>,
        dynamic?: (undefined | ((args: T) => string))
    ) {
        this.constantClassName = Object.values(flatten(constant)).join(' ');
        this.dynamicClassNameFn = dynamic;
    }
  
    extract(args: T): string | undefined {
        if (this.dynamicClassNameFn) {
            return concatClassNames(this.constantClassName, this.dynamicClassNameFn(args));
        }
        return this.constantClassName;
    }
};

const buttonTailwindClasses = new TailwindClasses<{active: boolean}>(
    {
        reuse: {
            other: 'text-base whitespace-nowrap cursor-pointer'
        },
        colour: 'bg-blue-500 text-white hover:bg-blue-300 active:bg-blue-500',
        border: 'border-0 rounded-full'
    },
    ({active}: {active: boolean}) => {
        const result: Record<string, string> = {};
        if (active) {
            result.colour = 'bg-blue-300';
        }
        return Object.values(result).join(' ');
    }
);

export type MyButtonProps = DetailedHTMLProps<
    ButtonHTMLAttributes<HTMLButtonElement>,
    HTMLButtonElement
> & {active: boolean };

const BlueButton = (props: MyButtonProps) => {
    const newProps = {
        ...props,
        className: buttonTailwindClasses.extract({active: props.active})
    };

    return (
        <button {...newProps} />
    );
}

Criticism

  • The class approach kind of bloats the code?
⚠️ **GitHub.com Fallback** ⚠️