react-if icon indicating copy to clipboard operation
react-if copied to clipboard

request: add tag rendering

Open hairyf opened this issue 9 months ago • 2 comments

Is there an existing issue or pull request for this?

  • [x] I have searched the existing issues and pull requests

Feature description

Adding a tag is a very interesting feature. As an advanced abstraction layer, it can determine the type of element to render based on the passed-in tag. This is particularly useful in scenarios that require dynamic rendering. For example, we may need to render different elements based on various conditions and add animation effects.

Desired solution

import { AnimatePresence, motion } from 'motion/react';
import { If } from 'react-if';

function List({ loading, list }) {
  const [tag, setTag] = useState('h1');
  return (
    <If condition={!loading} tag={AnimatePresence} initial={false}>
      <Then 
        tag={motion.div}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      >
        {/* ... */}
      </Then>
      <Else
        tag={motion.div} 
        initial={{ opacity: 0 }} 
        animate={{ opacity: 1 }} 
        exit={{ opacity: 0 }}
      >
        {/* ... */}
      </Else>
    </If>
  );
}

Corresponding implementation:

import type { JSX } from 'react'
import { createElement } from 'react'

export type WrapperTag = keyof JSX.IntrinsicElements | Function

export type WrapperProps<Kag extends keyof JSX.IntrinsicElements | React.FC | unknown> =
  { tag?: Kag } &
  (Kag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Kag] : unknown) &
  (Kag extends React.FC<infer P> ? P : unknown)

export function wrapper(tag: any, props: unknown, children?: React.ReactNode) {
  return tag ? createElement(tag, props, children) : children
}

// if.tsx
export type IfProps<Kag> = WrapperProps<Kag> & {
  condition?: BooleanLike
  children?: ReactNode
}

export function If<K extends WrapperTag>(props: IfProps<K>) {
  const { condition, children, tag, ...attrs } = props
  // ...
  return wrapper(tag, attrs, content)
}

Alternatives considered

none

Additional context

No response

hairyf avatar Apr 07 '25 09:04 hairyf

Open to a PR for this I suppose but can this not also be achieved with a typescript generic instead of a prop? i.e.

    <If<typeof AnimatePresence['div']> condition={!loading} initial={false}>
      <Then
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      >
        {/* ... */}
      </Then>
      <Else
        initial={{ opacity: 0 }} 
        animate={{ opacity: 1 }} 
        exit={{ opacity: 0 }}
      >
        {/* ... */}
      </Else>
    </If>

favna avatar Apr 07 '25 22:04 favna

Generics can only be used as types, and the initial property in the use case you described will be invalidated. If/Then/Else only be used as a regular <Fragment>

return tag ? createElement(tag, props, children) : children

hairyf avatar Apr 08 '25 02:04 hairyf