FormidableLabs/react-live

Using Styled Components with interpolated values produces mangled CSS

Closed this issue ยท 3 comments

๐Ÿ‘‹ I love this project! Super handy in our live docs. Thank you to all contributors.

I'm following your example on how to make Styled Components work with Live Preview. It works, as long as I don't use interpolated values.

If I start with:

const Row = styled.div`
  display: flex;
  align-items: center;
  margin: 10px 30px;
  padding: 10px 20px;
`;
render(<Row>Content</Row>);

all is well. This renders as expected. But if I add this:

const ClusterTableRow = styled.div`
  display: flex;
  align-items: center;
  text-decoration: ${'underline'};
  margin: 10px 30px;
  padding: 10px 20px;
  color: ${({ theme }) => theme.textColor};
  box-shadow: ${({ theme }) => theme.shadow1};
`;
render(<Row>Content</Row>);

then I get this CSS as a result (on the Row div that gets rendered):

  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-align-items: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  margin: 10px 30px;
  padding: 10px 20px;
  -webkit-underline#17222F0px: 2px 2px 0px rgba(11,33,74,0.06),0px -1px 1px 0px rgba(11,33,74,0.03);
  -ms-flex-underline#17222F0px: 2px 2px 0px rgba(11,33,74,0.06),0px -1px 1px 0px rgba(11,33,74,0.03);
  underline#17222F0px: 2px 2px 0px rgba(11,33,74,0.06),0px -1px 1px 0px rgba(11,33,74,0.03);

Notice how it doesn't matter where the interpolated values are in the string given to styled.div: The related CSS attributes are removed, and the interpolated values are all concatenated together at the end, as if it were some single style.

I have no code transformation function, so it's just rendering what it gets. I'm using noInline={true}.

Looks like a bug to me. The code is still good all the way down to the renderElementAsync(input, renderElement, errorCallback); call in react-live.es.js (in the input.code property), but it hasn't been interpreted yet. So something goes wrong deeper inside.

Hey @stefcameron

This seems to be caused by not interpolating the tagged template symbols.

const code = `
const ClusterTableRow = styled.div\`
  display: flex;
  align-items: center;
  text-decoration: \${'underline'};
  margin: 10px 30px;
  padding: 10px 20px;
  color: \${({ theme }) => theme.textColor};
  box-shadow: \${({ theme }) => theme.shadow1};
\`;
`;

Example: https://codesandbox.io/s/strange-ramanujan-4t5f5?file=/src/App.js

@JoviDeCroock Thanks for pointing that out. You are correct in that the backticks must be escaped in the string you're providing to the code prop of the LiveProvider, but that doesn't address the core of the issue, which is that interpolated values that are functions to access the theme result in mangled CSS.

I forked your sandbox and created this one:
https://codesandbox.io/s/vigorous-haze-73j4h?file=/src/App.js

All I changed is the theme object which is now:

const theme = {
  red: "red",
  textColor: "blue",
  shadow1: "0px 2px 8px 0px rgba(15,22,30,0.25)"
};

It still renders fine initially, but now try pasting this in the live code box that gets rendered (copied from my description above):

const Row = styled.div`
  display: flex;
  align-items: center;
  text-decoration: ${'underline'};
  margin: 10px 30px;
  padding: 10px 20px;
  color: ${({ theme }) => theme.textColor};
  box-shadow: ${({ theme }) => theme.shadow1};
`;
render(<Row>Content</Row>);

You should end-up with "Content" underlined (โœ… -- but that's not a function), blue (๐Ÿ›‘ ), and surrounded by a box shadow (๐Ÿ›‘ ). The blue and box shadow don't work because the CSS gets mangled at that point and the browser (Chrome and Canary, in my case) rejects it.

If you escape the backtick like styled.div\`, you'll just (rightly) get a syntax error:

SyntaxError: Expecting Unicode escape sequence \uXXXX (1:23)
1 : const Row = styled.div\`

Works in version 4+