Search code examples
reactjstypescriptstyled-componentstsx

Styled Components rendering Styled tag based on props


I am building an app and want to have Primary, Secondary and Tertiary buttons, I did not want to create 3 separate components which would be this same but with different Styled Component so I wanted to conditionally display the proper tag like in a code below, but I am getting an error JSX element type 'ButtonStyles' does not have any construct or call signatures. What am I doing wrong? Thanks

import React from "react";
import styled from "styled-components";

const PrimaryStyles = styled.button`
  width: 26rem;
  height: 5rem;
  background-image: ${({ theme }) => theme.gradients.ry};
  border: none;
  border-radius: 4px;
  color: ${({ theme }) => theme.black};
  font-size: 2rem;
  box-shadow: 3px 3px 30px ${({ theme }) => theme.red + "70"};
  cursor: pointer;
  padding: 0 1rem;
`;

const SecondaryStyles = styled.button``;

const TertiaryStyles = styled.button`
  border: none;
  background: none;
  color: ${({ theme }) => theme.grey};
`;

interface IProps {
  type: "button" | "reset" | "submit";
  children: React.ReactNode;
  onClick: React.MouseEventHandler<HTMLButtonElement>;
  primary?: boolean;
  secondary?: boolean;
  tertiary?: boolean;
}
export const Button: React.FC<IProps> = ({
  children,
  onClick,
  type,
  primary = true,
  secondary,
  tertiary,
}) => {
  const ButtonStyles = primary
    ? PrimaryStyles
    : secondary
    ? SecondaryStyles
    : tertiary
    ? TertiaryStyles
    : null;
  return (
    <ButtonStyles type={type} onClick={onClick}>
      {children}
    </ButtonStyles>
  );
};


Solution

  • That's because of this logic:

    const ButtonStyles = primary
        ? PrimaryStyles
        : secondary
        ? SecondaryStyles
        : tertiary
        ? TertiaryStyles
        : null;
    

    If it's not primary, secondary or tertiary it will be null. So your code will setting ButtonStyles as NULL and then you cannot do this:

     return (
        <ButtonStyles type={type} onClick={onClick}>
          {children}
        </ButtonStyles>
      );
    

    A solution to this is remove the NULL option, something like:

    const ButtonStyles = primary
        ? PrimaryStyles
        : secondary
        ? SecondaryStyles
        : TertiaryStyles;
    

    Another approach, to avoid the ternary conditional operator would be doing something like this:

    1. Create an object containing the 3 styles options:
    interface IButtonStyles {
      [key: string]: StyledComponent<"button", any, {}, never>;
    }
    
    const buttonStyleTypes: IButtonStyles = {
      primary: PrimaryStyles,
      secondary: SecondaryStyles,
      tertiary: TertiaryStyles,
    };
    
    1. Change your IProps to something like that:
    interface IProps {
      type: "button" | "reset" | "submit";
      children: React.ReactNode;
      onClick: React.MouseEventHandler<HTMLButtonElement>;
      styleType?: string;
    }
    
    1. The Button component to this:
    const Button: React.FC<IProps> = ({
      children,
      type,
      styleType = "primary",
    }) => {
    
      const ButtonStyles = buttonStyleTypes[styleType];
    
      return <ButtonStyles type={type}>{children}</ButtonStyles>;
    };
    
    1. And finally, you can create the button like this:
    <Button type={"button"}>primary</Button>
    <Button type={"button"} styleType='secondary'>secondary</Button>
    <Button type={"button"} styleType='tertiary'>tertiary</Button>