javascriptreactjsdesign-patterns

How to add extra style properties to the existing ones of a defined component using Component Composition?


I came across the Component Composition Design Pattern in React, which is said by the tutor to be analogue to inheritance in OOP. My question is, if I want to extend the defined props of the Button, how do you do this?!

SIDE NOTE: I know you would do this with CSS in the first place, but I ran outta ideas to customize the tutor's example.

In the second code snippet I tried adding both, borderColor: "blue" and color="red" to the style attribute of the BigSuccessButton to try different approaches of appending stuff. But with the style attribute the entire content defined in the Button Component will be killed. So I will only see blue borders.

So I thought of adding a new prop and using it. But if last mentioned is the way to do this, how can I append this thing?

Those are the Composition Components, with Button being the Super Class:

export const Button = ({ size, bgColor, text, ...props }) => {
  console.log(props);
  return (
    <button
      style={{
        padding: size === "large" ? "32px" : "8px",
        fontSize: size === "large" ? "32px" : "16px",
        backgroundColor: bgColor,
      }}
      {...props}
    >
      {text}
    </button>
  );
};

export const DangerButton = (props) => {
  return <Button {...props} bgColor="red" />;
};

export const BigSuccessButton = (props) => {
  return (
    <Button
      {...props}
      size="large"
      bgColor="green"
      
    />
  );
};

Here I wanna add text color to BigSuccessButton:

import { BigSuccessButton, DangerButton } from "./Composition";


function App_FuncProg() {
  return (
    <>
      {/* <RecursiveComponent data={nestedObject} /> */}
      <DangerButton text="Danger" />
      <BigSuccessButton text="Yippieh!" style={{borderColor: "blue"}}  color="red" />
    </>
  );
}

export default App_FuncProg;

Solution

  • You've kind of mixed two patterns -- passing props and mapping them into styles; and trying to override the style prop. Passing props probably isn't the way because you don't want to end up having to map new props to the style object every single time you want to customize a new property (though design system libraries like Chakra do do this internally, but it's comprehensive and you don't want to reinvent the whole wheel here).

    That said, your mapping of size is more acceptable because it actually has semantic meaning, it actually does something (picking the pixel size of fontSize and padding). So I kept that part.

    What you really want here I think is to add support for merging the style prop.

    This sort of "pass through" approach gives you a high degree of flexibility at low cost by exposing the entire style CSS API to the parents; whilst at the same time, retaining the default styling. The downside is it's maybe a little more ugly. Sometimes, if you want more control, you'd go purely with the mapping approach and add specific support via dev-defined props for the things you want exposed. Like you did with the size prop. It really depends on what you want your component API to look like.

    A smaller thing: you really want to spread the props after the default ones if you want them to be overridable. The order matters.

    export const Button = ({ text, size, style, ...props }) => {
      console.log(props);
      return (
        <button
          style={{
            padding: size === "large" ? "32px" : "8px",
            fontSize: size === "large" ? "32px" : "16px",
            ...style,
    
          }}
          {...props}
        >
          {text}
        </button>
      );
    };
    
    export const DangerButton = ({style, ...props}) => {
      return <Button  style={{backgroundColor: 'red', ...style}} {...props}/>;
    };
    
    export const BigSuccessButton = ({style, ...props}) => {
      return (
        <Button
          size="large"
          style={{backgroundColor: 'green', ...style}}
          {...props}    
        />
      );
    };
    
    import { BigSuccessButton, DangerButton } from "./Composition";
    
    
    function App_FuncProg() {
      return (
        <>
          {/* <RecursiveComponent data={nestedObject} /> */}
          <DangerButton text="Danger" />
          <BigSuccessButton text="Yippieh!" style={{borderColor: "blue", color: 'red'}} />
        </>
      );
    }
    
    export default App_FuncProg;
    

    An example of the opposing "first class props styling API" approach is Chakra's API: https://chakra-ui.com/docs/styled-system/style-props. But again, this is obviously a very complete third party library and they've spent a lot of time making this nice to use and expose every single option.

    (SIDE NOTE: I know you would do this with CSS in the first place, but I ran outta ideas to customize the tutor's example.)

    To be honest, in React, you don't usually go with separate CSS files like this. "CSS-in-JS" is now the norm. So inline styling like this is actually quite normal and not frowned on. There's whole libraries built around the philosophy (not necessarily using style attribute, but embedding the CSS in the component -- these things are complex and inject CSS into the DOM and then dynamically create class attributes). Have a look at styled-components and emotion. And Chakra etc has their own baked-in based on things like emotion also.