2
\$\begingroup\$

I created a react-auto-height library:

A React component that animates height: auto children when their content is changed, using CSS transitions.

It started simple, inspired by a CSS-Tricks article, but I needed to cover more edge cases, most notably to allow multiple levels of components to use <AutoHeight> without knowing about each other => handling nested elements whose height attribute is in transition.

Am I using any dangerous patterns that could affect other React components unrelated to height animation? (obviously, it will affect other height animation techniques, but I want to be a "good citizen" regarting unrelated features...). If yes, please advice possible mitigation (or if you know there isn't anything I can do except document better).

Note: I am not much concerned about security or performance - unless someone will notice I missed something important (there shouldn't be any new vulnerability that wouldn't exist otherwise + CSS transitions are already expensive, iterating over DOM children adds less than 2ms even in IE11). And I rely on manual testing of a static build of a storybook for now.

import React, {memo, useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import './index.css'

const PREV_HEIGHT = 'data-react-auto-height-start-value'
const AutoHeight = ({children, className, ...props}) => {
  const ref = useRef()

  useEffect(() => {
    const {current: el} = ref
    if (!el) {
      return
    }

    // find descendants created by nested AutoHeights and adjust future height of current element by the future heights of children
    // skip for first render
    let adjustBy = 0
    if (el.style.height) {
      el.setAttribute(PREV_HEIGHT, el.style.height)
      let descendants = Array.from(el.firstChild.children)
      for (let child of descendants) {
        let prevHeight = child.getAttribute(PREV_HEIGHT)
        if (prevHeight) {
          child = child.firstChild // skip the outer wrapper
          adjustBy += child.scrollHeight - parseInt(prevHeight)
        }
        if (child.children && child.children.length) {
          Array.from(child.children).forEach(grandChild => {
            if (grandChild.getAttribute) {
              descendants.push(grandChild)
            }
          })
        }
      }
    }

    el.style.height = el.firstChild.scrollHeight + adjustBy + 'px'
  })

  // inner div used in el.firstChild
  return (
    <div ref={ref} className={`react-auto-height ${className}`} {...props}>
      <div>{children}</div>
    </div>
  )
}

export default memo(AutoHeight)

AutoHeight.propTypes = {
  children: PropTypes.node,
  /** Props are propagated to the outer wrapper div - including style, className, data-test-id, ... */
  '...props': PropTypes.string,
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.