Skip to content

Instantly share code, notes, and snippets.

@loburets
Last active September 13, 2021 04:54
Show Gist options
  • Save loburets/459de25eef578d72782c48ee607d37ea to your computer and use it in GitHub Desktop.
Save loburets/459de25eef578d72782c48ee607d37ea to your computer and use it in GitHub Desktop.
React hook to check element sizes and do it in 1 render cycle so in the next one they can be covered by preloader/sekeleton component using the sizes
import { useState, useEffect, useRef } from 'react'
/**
* This hook provides an array of refs with a fixed length.
* Then you can set the refs to any of your elements and on the next render tick, you will now size of such elements.
*
* Example of usage:
*
* const reservedQuantityOfElements = 2
* const [elementsToBeSized, elementsSizes, temporaryShowElementsAsIs] = useElementsSizes(reservedQuantityOfElements)
*
* // here you can use elementsSizes[0] and elementsSizes[1] to see width, height and margins for the 2 divs below
*
* return (
* <div ref={elementsToBeSized[0]}>
* <div ref={elementsToBeSized[1]}>
* </div>
* </div>
* )
*
* "temporaryShowElementsAsIs" is an optional boolean variable.
* It will be true during one render cycle to let components show the element in the original state.
* It can be helpful, for example, for the case when the element will be hidden by a preloader,
* but to know the size of the preloader, we need to render the component 1 time without it.
*/
export default function useElementsSizes (quantityOfElementsWithSkeletons) {
const elementsToBeSized = []
for (let i = 0; i < quantityOfElementsWithSkeletons; i++) {
elementsToBeSized[i] = useRef(null)
}
const [temporaryShowElementsAsIs, setTemporaryShowElementsAsIs] = useState(false)
const [isSizesMeasured, setIsSizesMeasured] = useState(false)
const [elementsSizes, setElementsSizes] = useState([])
useEffect(_ => {
if (typeof window === 'undefined') {
return
}
// When these conditions are satisfied, it is already the third render.
// All the job of this hook has been already done
// So we don't need to have all the measured elements anymore, now they can be hidden or changed if required
if (isSizesMeasured && temporaryShowElementsAsIs) {
setTemporaryShowElementsAsIs(false)
}
// It is the third render or some of the following renders of the component
// But all the sizes are already defined, so nothing to be done in this hook and we just exit it
if (isSizesMeasured) {
return
}
// The first render, so the hook will let the component know that it should temporarily show the elements if
// they were hidden or changed someway. For example, the elements could be changed by "skeleton" loaders.
// In this case, the skeletons will be temporarily disabled for one render circle to let the hook
// measure the original size.
if (!temporaryShowElementsAsIs) {
setTemporaryShowElementsAsIs(true)
return
}
const sizes = []
// It is a second render, so if some elements were not ready to be measured,
// they already changed by the previous render and we can measure them now
elementsToBeSized.forEach((element, index) => {
if (!element.current) {
return
}
const style = getComputedStyle(element.current)
sizes[index] = {
height: element.current.offsetHeight,
width: element.current.offsetWidth,
marginBottom: parseFloat(style.marginBottom)
}
})
setElementsSizes(sizes)
setIsSizesMeasured(true)
}, [temporaryShowElementsAsIs, isSizesMeasured])
return [elementsToBeSized, elementsSizes, temporaryShowElementsAsIs]
}
@loburets
Copy link
Author

loburets commented Sep 13, 2021

Example how it can be used to adjust skeleton sizes for the content:

const TitileWithSkeleton = ({
  isLoading = false,
  title = '',
}) => {
  // in this example it is just one <h4> element to be measured, but it can be few ones, it is why the array of refs is returned
  const quantityOfElements = 1
  const [elementsToBeSized, elementsSizes, temporaryShowElementsAsIs] = useElementsSizes(quantityOfElements)
  const showSkeletons = isLoading && !temporaryShowElementsAsIs

  return (
    <>
        { !showSkeletons &&
          <h4 ref={elementsToBeSized[0]}>{title}</h4>
        }
        { showSkeletons &&
          <Skeleton
            size={elementsSizes[0]}
          />
        }
    </>
  )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment