Search code examples
javascriptcssreactjsstyled-componentsemotion

How to correctly escape css functions for styled components?


Context: I want to animate two different components using the same css styles. Animations depend on some props being passed to the AnimatedBox component, then for example, if only props.right is true, then animate only the right border. I thought a css function would do the job, so for not repeating the code, I only create the element and call the function. However, I'm getting the following error, no matter what I do, and the components don't behave as expected:

Functions that are interpolated in css calls will be stringified.

Here is a bit of my code (it's truncated).

const AnimatedBox = props => (
    <>
        {props.absolute
            ? 
            <MaskWrapper>
                <BeforeMask {...props}/>
                <AfterMask {...props}/>
            </MaskWrapper>
            :
            <AnimatedDiv {...props}></AnimatedDiv>}
    </>
)

export default AnimatedBox

const commonConfig = css`
    box-sizing: inherit;
    position: absolute;
    content: '';
    border: 1px solid transparent;
    border-radius: 3px;
`
const beforeConfig = css`
    top: 0;
    left: 0;
    border-top-color: ${({top}) => `${top && "#60d75a"}`};
    border-right-color: ${({right}) => `${right && "#60d75a"}`};
    animation: ${expandWidth} 0.25s ease-out forwards,
    ${expandHeight} 0.25s ease-out 0.25s forwards;
`
const afterConfig = css`
    right: 0;
    bottom: 0;
    animation: ${({bottom, left}) => `${
        (bottom && left) 
        ?
            `
                ${bottomBorderColor} 0s ease-out 0.5s forwards,
                ${leftBorderColor} 0s ease-out 0.5s forwards,
                ${expandWidth} 0.25s ease-out 0.5s forwards,
                ${expandHeight} 0.25s ease-out 0.75s forwards;
            `
        : (
            bottom 
            ?
                `   
                    ${bottomBorderColor} 0s ease-out 0.5s forwards,
                    ${expandWidth} 0.25s ease-out 0.5s forwards,
                    ${expandHeight} 0.25s ease-out 0.75s forwards;
                `
            : (
                left 
                &&
                    `
                        ${leftBorderColor} 0s ease-out 0.5s forwards,
                        ${expandWidth} 0.25s ease-out 0.5s forwards,
                        ${expandHeight} 0.25s ease-out 0.75s forwards;
                    `
                )
            )    
        }`
    };
`
const BeforeMask = styled.div`
    ${beforeConfig}
`
(...)

expandWidth, expandHeight, bottomBorderColor, etc, are all keyframe animations. I just need to know how to write those functions and pass them to the styled component I want without getting that error.

SOLVED

As @jsejcksn suggested, I had to put the styled divs into the component's scope. Then I made the css functions explicitly take parameters and make some syntactical changes, and then pass the required props to them.

const AnimatedBox = props => {
    (...)
    const BeforeMask = styled.div`
        ${beforeConfig(props.top, props.right)}
    `
    (...)

    return (
    <>
        {props.absolute
            ? 
            <MaskWrapper>
                <BeforeMask {...props}/>
                <AfterMask {...props}/>
            </MaskWrapper>
            :
            <AnimatedDiv {...props}></AnimatedDiv>}
    </>
)}

const beforeConfig = (top, right) => css`
    top: 0;
    left: 0;
    border-top-color: ${top && "#60d75a"};
    border-right-color: ${right && "#60d75a"};
    animation: ${expandWidth} 0.25s ease-out forwards,
    ${expandHeight} 0.25s ease-out 0.25s forwards;
`
const afterConfig = (bottom, left) => css`
    right: 0;
    bottom: 0;
    animation: ${
        (bottom && left) 
        ?
        css`
            ${bottomBorderColor} 0s ease-out 0.5s forwards,
            ${leftBorderColor} 0s ease-out 0.5s forwards,
            ${expandWidth} 0.25s ease-out 0.5s forwards,
            ${expandHeight} 0.25s ease-out 0.75s forwards;
        `
        : 
        (
            bottom 
            ?
            css`  
                ${bottomBorderColor} 0s ease-out 0.5s forwards,
                ${expandWidth} 0.25s ease-out 0.5s forwards,
                ${expandHeight} 0.25s ease-out 0.75s forwards;
            `
            : 
            (
                left 
                &&
                css`
                    ${leftBorderColor} 0s ease-out 0.5s forwards,
                    ${expandWidth} 0.25s ease-out 0.5s forwards,
                    ${expandHeight} 0.25s ease-out 0.75s forwards;
                `
            )
        )    
    }
`

Solution

  • I think you are expecting that your functions will be evaluated, but that is not what happens during interpolation. Like in the example below, your functions are simply being stringified in the template literal.

    // where is name defined?
    const str = `hello ${(name) => `${name}`}!`;
    console.log(str); //=> "hello (name) => `${name}`!"

    You'll need to define the styles in scope with the variables that you are expecting to be able to use (e.g. inside the component if these are coming from the component's props):

    const name = 'Earth';
    
    const str = `hello ${name}!`;
    console.log(str); //=> "hello Earth!"

    const Component = (props) => {
      const divStyle = css`
        background-color: ${props.bgColor};
        color: white;
      `;
    
      const VariableColorDiv = styled.div`${divStyle}`;
    
      return <VariableColorDiv>hello world</VariableColorDiv>;
    };