Search code examples
javascriptreactjsstyled-componentsstorybook

Storybook: Objects are not valid as a React child when not using backticks after .attrs


I have the following simple styled-component:

const Button = styled.bytton.attrs(({primary}) => ({
  className: primary ? 'something' : 'something-else'
})

Now for some reason unless I append the backticks in the ed such as:

const Button = styled.bytton.attrs(({primary}) => ({
  className: primary ? 'something' : 'something-else'
})`` // here

I will get an error from storybook:

Objects are not valid as a React child (found: object with keys {$$typeof, render, attrs, >componentStyle, displayName, foldedComponentIds, styledComponentId, target, withComponent, >warnTooManyClasses, toString}). If you meant to render a collection of children, use an array >instead.

Ideally, I would like to avoid putting random backticks in the codebase just to suppress the error...


Solution

  • If all you're using the styled-components api for is creating common components that share logic around toggling classNames you may want to just create a higher order component.

    function withProps(Component, propSetter) {
      return (props) => {
        const additionalProps = propSetter(props)
        return <Component {...props} {...additionalProps} />
      }
    }
    
    const Button = withProps('button', ({ primary }) => ({
      className: primary ? 'something' : 'something-else'
    })
    

    But to give context on the original question:

    The backticks as mentioned aren't as random as it might look. They may feel odd when you see them empty but they do serve a purpose. To see the purpose of them it might be easier to look at what actually happens when the backticks or tagged template literals get compiled into regular JavaScript.

    // Before
    styled.button`
      color: green;
    `
    
    // After
    styled.button(['color: green;'])
    

    As you can see the string got passed into the function that is returned from styled.button, styled.attrs return the same function. Now the example above doesn't make much use of the power of the backticks. The real power comes from function calls.

    // Before
    styled.button`
      color: ${props => props.theme.color};
    `
    
    // After
    styled.button(['color: ', ';'], props => props.theme.color)
    

    As you can see it has broken apart the template from the inputs. The first argument to the function being the template, and the following arguments are inputs that go after each part in the array. This is how styled components can pass you props to do special things within your components.

    styled.button and styled.button.attrs() both return a function to be called in this way with the tagged template literals. If they returned a renderable React component instead then you wouldn't be able to provide the css at all.

    When you try to render something without the backticks the value you have in Button is an object with information ready to create your component. Until you call the object either via () or the `` then you won't have a component you can render.

    If the backticks are too weird then you might feel more comfortable replacing them with ()? Alternatively you could think about creating a wrapper function or something to make sure they always get called.