0

I am trying to create a few variants of buttons and decided to use Styled Components to accomplish this. However, I am having some issues trying to pass a variant button from a child to the parent that holds all the variants.

Currently, I have a Navbar that imports the Button.js component. I would like to have the option to change the button within the Navbar.js like this:

<Button default label="Game" />
<Button secondary label="Dictionary" />

However, within my Button.js file, I have tried switching the style of the button through using props, but this does not work.

import React from 'react'
import styled from 'styled-components'

const ButtonStyle = styled.span`
  // shared styles ...
  // props
  background: ${props => props.primary ? "red" : "white"};

`

function Button({ label }) {
  return <ButtonStyle>{label}</ButtonStyle>
}

export default Button

I've also tried extending styles, but the problem is that the name, TomatoeButton, does not get passed to the Navbar.js component.

const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

Shouldn't this take in the primary attribute and change the color of the background?

Tyler Morales
  • 1,440
  • 2
  • 19
  • 56

1 Answers1

0

The problem with the above is that Button must accept a className prop from styled(Button). Then it needs to pass it down to StyledButton:

import * as React from 'react'
import styled from 'styled-components'

const ButtonStyle = styled.span`
  // shared styles ...
  // props
  background: ${props => props.primary ? "red" : "white"};
`

export function Button({ className, label }) {
  return <ButtonStyle className={className}>{label}</ButtonStyle>
}

const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

export default TomatoButton;

Styled-components works by assigning a unique className to the element. If you're using styled.HTMLElement (styled.div, styled.img, ..etc), then it does it automatically. However, when you wrap a React component with styled, then it assumes that it'll accept and manually assign the className to the component.

For example:

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

// the result of "RedButton" is <button className="sc-hBEYos dWjUC">{children}</button>
const RedButton = styled.button`
  color: red;
`; 

// the result of "Example" is <button className="sc-hBEYos dWjUC">Test</button>
const Example = () => <RedButton>Test</RedButton>

When you're using a React component, then it must accept and assign the className and children (and possibly other incoming props) and assign them manually:

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

// the result of "Button" is <button className={className}>{children}</button>
const Button = ({ className, children }) => (
  <button className={className}>
    {children}
  </button>
);

// the result of "BlueButton" is <button className="sc-hBEYos dWjUC">{children}</button>
const BlueButton = styled(Button)`
  color: blue;
`;

// the result of "Example2" is <button className="sc-hBEYos dWjUC">Test</button>
const ExampleTwo = () => <BlueButton>Test</BlueButton>

As you can see, styling composed React components can get a bit redundant and verbose if you try wrapping them with other styled components (you have to keep passing down className), so instead I'd recommend using the Referring to other components approach.

In short, keep the styled-components as styled.HTMLElement and if you need to wrap them, then use the composing (referring to other components) method mentioned above:

import styled from "styled-components";

const Button = styled.button`
  color: red;
`;

const BlackBlueButton = styled.div`
  background: black;

  ${Button} {
    color: blue;
  }
`;

// In short, the result is that the "Button" below gets two classNames, one from 
// itself (styled.button) and another from "BlackBlueButton".
const Example = () => (
  <BlackBlueButton>
    <Button>Test</Button>
  </BlackBlueButton>
);

On a separate note, if you're trying to create flexible components, then take a look at this answer.

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • Thanks, this helps. I was trying to look for a solution that made it simple to set an indicator on a button, such as primary, and have that get passed to the button component to determine the specific styling. – Tyler Morales Apr 15 '21 at 04:07