Search code examples
javascriptreactjsstyled-components

Pass transient props through nested styled components and React function components


I've been struggling with styled components and transient props. See code below.

  • I have a styled component, let's call it StyledLabel, that receives a transient prop.
  • The StyledLabel is used in a function component, let's call it IntermediateLabel.
  • The IntermediateLabel passes the props it receives to the StyledComponent.
  • The IntermediateLabel is restyled in a styled component, let's call it ExtraStyledLabel

Now to the problem, when passing the transient prop to the ExtraStyledLabel it is not passed to the IntermediateLabel. But when using the IntermediateLabel directly, it is passed to the StyledLabel.

Is it not possible to restyle function components? Am I doing something wrong or is it a bug?

Here is the code, and Codepen https://codepen.io/tengl/pen/abxgzdL

const { createRoot } = ReactDOM;
const { useState, forwardRef } = React;
const { createGlobalStyle } = styled;

const GlobalStyle = createGlobalStyle`
  html, body, #app {
    height: 100%;
    min-height: 100%;
  }
`;

const App = styled.div`
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
`;

const StyledLabel = styled.p`
    color: ${(p) => (p.$isSelected ? "green" : "red")};
    font-family: "Roboto Mono", monospace;
    font-size: 20px;
`;

// WORKS
const IntermediateLabelStyled = styled(StyledLabel)``;

// WORKS
const IntermediateLabelComponent = forwardRef(({ children, ...props }) => {
    console.log(props.$isSelected);
    return <StyledLabel {...props}>{children} </StyledLabel>;
});

// WORKS
const IntermediateLabelForwardRef = ({ children, ...props }) => {
    console.log(props.$isSelected);
    return <StyledLabel {...props}>{children} </StyledLabel>;
};

// WORKS
const ExtraStyledLabelStyledComponents = styled(IntermediateLabelStyled)`
    font-weight: bold;
    font-size: 30px;
`;

// DOES NOT WORK
const ExtraStyledLabelComponent = styled(IntermediateLabelComponent)`
    font-weight: bold;
    font-size: 30px;
`;

// DOES NOT WORK
const ExtraStyledLabelForwardRef = styled(IntermediateLabelForwardRef)`
    font-weight: bold;
    font-size: 30px;
`;

const Container = () => (
    <App>
        <GlobalStyle />
        <StyledLabel $isSelected>React 18 + styled-components 6. Transient props in intermediate components</StyledLabel>
        <StyledLabel $isSelected>Green text is ok, red is not</StyledLabel>
        <ExtraStyledLabelStyledComponents $isSelected={true}>With styled component</ExtraStyledLabelStyledComponents>
        <ExtraStyledLabelComponent $isSelected={true}>With React component</ExtraStyledLabelComponent>
        <ExtraStyledLabelForwardRef $isSelected={true}>With forwardRef</ExtraStyledLabelForwardRef>
        <IntermediateLabelComponent $isSelected={true}>Function component directly</IntermediateLabelComponent>
    </App>
);

const root = createRoot(document.getElementById("app"));
root.render(<Container />);

Solution

  • It is not possible since it was designed that way. Stated in the documentation (https://styled-components.com/docs/api#transient-props) that:

    If you want to prevent props meant to be consumed by styled components from being passed to the underlying React node or rendered to the DOM element, you can prefix the prop name with a dollar sign ($), turning it into a transient prop.

    This means that it's behaving correctly for the React node to not be able to access the transient props. You can use normal props and filtering them manually instead.