Skip to content

Passing the as prop to a multiple style layered component only applies the first styles #3115

Open
@rapgodnpm

Description

@rapgodnpm

Description

Hello,

Thank you for your contribution to this wonderful library. You can see the bug in the reproduction link. Bellow is an explanation with the problem and a fix.

The issue is in the src/styled-system/jsx/factory.js file with this line:

let { as: Element = __base__, ...elementProps } = props

If as is a string, for example, h1 it will call:

return createElement(Element, {
        ref,
        ...elementProps,
        className: classes(),
      })

And will create an h1 with the classes and styles from the component. The problem is that __base__ is a react component. In our case comp B which renders comp A. They will be ignored and the as value will be used.

To fix it, I've changed the code to be the following in factory.js:

import { createElement, forwardRef } from 'react'
import { getDisplayName } from './factory-helper.js'
import { css, cx } from '../css/index.js'

function createStyledFn(Dynamic) {
  const __base__ = Dynamic.__base__ || Dynamic
  return function styledFn(template) {
    const styles = css.raw(template)

    const StyledComponent = /* @__PURE__ */ forwardRef(function StyledComponent(
      props,
      ref,
    ) {
      let { as: Element = __base__, ...elementProps } = props

      if (typeof __base__ !== 'string') {
        Element = __base__
      }

      function classes() {
        return cx(css(__base__.__styles__, styles), elementProps.className)
      }

      return createElement(Element, {
        ref,
        ...elementProps,
        as: props.as,
        className: classes(),
      })
    })

    const name = getDisplayName(__base__)

    StyledComponent.displayName = `styled.${name}`
    StyledComponent.__styles__ = styles
    StyledComponent.__base__ = __base__

    return StyledComponent
  }
}

function createJsxFactory() {
  const cache = new Map()

  return new Proxy(createStyledFn, {
    apply(_, __, args) {
      return createStyledFn(...args)
    },
    get(_, el) {
      if (!cache.has(el)) {
        cache.set(el, createStyledFn(el))
      }
      return cache.get(el)
    },
  })
}

export const styled = /* @__PURE__ */ createJsxFactory()

This way the styles are kept from the other components and the as thingy also works.

Link to Reproduction

https://stackblitz.com/edit/vitejs-vite-enjwupkj?file=src%2FApp.tsx

Steps to reproduce

  1. Use template literal syntax
  2. Create at least 2 components like this: A = styled.spansome styles and B = styled(A)other styles
  3. Use the last styled component with the as prop: fdfd
  4. The styles from comp A are not applied

JS Framework

No response

Panda CSS Version

0.48.0

Browser

No response

Operating System

  • macOS
  • Windows
  • Linux

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions