Styled-components: Dynamic component tag without creating new styled components

Created on 15 Mar 2018  路  3Comments  路  Source: styled-components/styled-components

We have components that needs to have their html tag set dynamically based on props/children type.
We are concerned about potential memory leaks from creating styled components in the render method.

For example we have a heading component that needs to set its h<level> based on a prop.
Another example is a component that takes on the type of its only child.

The first solution I thought about was caching the results of .withComponent as we go and then reusing them.
Second solution would be to just create all the possible components before rendering.

Has anyone run into this? Any better solutions?

Most helpful comment

I have some rather untested code on CodeSandbox that I wrote because of ppl asking for it :) https://codesandbox.io/s/n5nRmZBv4

I don鈥檛 think it鈥檚 a good idea to create a whole lot of tags before hand as those can really be a lot after a while. So try to avoid that.

Also, consider making more separate components if possible. Small and specialised styledcomponents definitely beat larger ones in maintainability if you have a good theme ;)

All 3 comments

I have some rather untested code on CodeSandbox that I wrote because of ppl asking for it :) https://codesandbox.io/s/n5nRmZBv4

I don鈥檛 think it鈥檚 a good idea to create a whole lot of tags before hand as those can really be a lot after a while. So try to avoid that.

Also, consider making more separate components if possible. Small and specialised styledcomponents definitely beat larger ones in maintainability if you have a good theme ;)

Be great to see this incorporated into the package. You say that we should be creating smaller components however I have a use case for a button being a span within an anchor.

I had a use case for this in regards to typography. Instead of setting individual styles for each HTML tag, we wanted a system where any heading could inherit the styles of any other heading. The use case is as follows:

  • Art Director says that TitleA in ComponentB needs to have less visual weight than the other identical h1 tags in ComponentA.
  • As all of these ComponentXs live in section tags, they all should have a semantic h1 tag.
  • Headings should inherit from a set of heading styles ( different font-size, line-height, etc )

To fix this, I implemented a solution like this:

const typoGenerator = (props) => {
  const [base, variant] = props.styleBase.like.split('-');
  const typeStyles = {
    heading: {
      // these top level bases are only for font-family really.
      1: {
        'font-family': props.theme.font.heading,
        'font-size': props.theme.modularScale.large,
        'line-height': `${props.theme.modularScale.large.split('em')[0] * 1.5}em`
        // these variants are only for size really
      },
      2: {
        'font-family': props.theme.font.heading,
        'font-size': props.theme.modularScale.xlarge,
        'line-height': `${props.theme.modularScale.xlarge.split('em')[0] * 1.5}em`
      },
      3: {
        'font-family': props.theme.font.heading,
        'font-size': props.theme.modularScale['2xlarge'],
        'line-height': `${props.theme.modularScale['2xlarge'].split('em')[0] * 1.5}em`
      },
      4: {
        'font-family': props.theme.font.heading,
        'font-size': props.theme.modularScale['3xlarge'],
        'line-height': `${props.theme.modularScale['3xlarge'].split('em')[0] * 1.5}em`
      },
      5: {
        'font-family': props.theme.font.heading,
        'font-size': props.theme.modularScale['4xlarge'],
        'line-height': `${props.theme.modularScale['4xlarge'].split('em')[0] * 1.5}em`
      }
    },
    paragraph: {
      1: {
        'font-family': props.theme.font.serif,
        'font-size': props.theme.modularScale.small,
        'line-height': `${props.theme.modularScale.small.split('em')[0] * 1.5}em`
      },
      2: {
        'font-family': props.theme.font.serif,
        'font-size': props.theme.modularScale.base,
        'line-height': `${props.theme.modularScale.base.split('em')[0] * 1.5}em`
      },
      3: {
        'font-family': props.theme.font.serif,
        'font-size': props.theme.modularScale.medium,
        'line-height': `${props.theme.modularScale.medium.split('em')[0] * 1.5}em`
      }
    },
    dec: {
      1: {
        'font-family': props.theme.font.sans,
        'font-size': props.theme.modularScale.small,
        'line-height': `${props.theme.modularScale.small.split('em')[0] * 1.5}em`
      }
    }
  };
  const stringifiedClasses = Object.entries(typeStyles[base][variant])
    .reduce((acc, curr) => `${acc}${curr.map(property => `${property}`).join(':')};`, '');
  return stringifiedClasses;
};

const buildType = (props) => {
  const StyledTypography = styled(`${props.element}`)`
    ${props => typoGenerator(props)}
    ${props => props.styleBase.declarations}
  `;
  const element = React.createElement(StyledTypography, props);
  return element;
};

const Typography = props => (
  <React.Fragment>
    {buildType(props)}
  </React.Fragment>
);

And called like:
<Typography element="h1" styleBase={{ like: 'heading-1', declarations: 'line-height:1em;' }}>This is a heading</Typography>

The as API in v4 is promising for something like this, but I have found issues with v4 of styled-components playing nice with react-testing-library, so I have yet to migrate.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

garmjs picture garmjs  路  3Comments

const-g picture const-g  路  3Comments

ekfuhrmann picture ekfuhrmann  路  3Comments

oscar-b picture oscar-b  路  3Comments

rohmanhm picture rohmanhm  路  3Comments