How I made a React component that converts an Image into a background image

Learn how to create a React component that converts an image into a background image of its parent element.

How I made a React component that converts an Image into a background image

The context

For developing one of my apps I got an HTML template that I needed to convert to React.Js (Next.js). In the original HTML-only massive code, there is a utility CSS class named 'bg-img':

<div>
  <img class="bg-img" src="/path/to/image.jpg" />
  <!-- Other element: A headline and a subtitle for example -->
</div>

Turns out that this class is used by the following script among the assets files:

$(".bg-img").parent().addClass('bg-size');

jQuery('.bg-img').each(function () {
  var el = $(this),
    src = el.attr('src'),
    parent = el.parent();

  parent.css({
    'background-image': 'url(' + src + ')',
    'background-size': 'cover',
    'background-position': 'center',
    'display': 'block'
  });

  el.hide();
});

That script is injected traditionally into the HTML document right before the closing body tag. Basically what it tells the browser to do is to get the images (with the 'bg-img' class) src attribute, and set it as the background image of its parent/container element.

The React equivalent

Without turning around the bucket I'll show you the complete React component I used to replace the jQuery script above in Typescript:

// ImageToBg.tsx
import { useEffect, useRef } from "react"

type Props = {
  src: string;
}

const ImageToBg = ({ src }: Props) => {
  const ref = useRef<HTMLImageElement>(null);

  useEffect(() => {
    const currentRef = ref.current;
    if (!currentRef) return;
    const parent = currentRef.parentElement;
    if (!parent) return;

    parent.classList.add('bg-size');

    parent.style.backgroundImage = 'url(' + src + ')';
    parent.style.backgroundSize = 'cover';
    parent.style.backgroundPosition = 'center';
    parent.style.display = 'block';

    currentRef.style.display = 'none';
  })

  return (
    <img
      ref={ref}
      src={src}
      alt=""
    />
  )
};

export default ImageToBg;

Then we would use that component, let's name it ImageToBg.tsx, like this:

  <div>
-   <img class="bg-img" src="/path/to/image.jpg" />
+   <ImageToBg src="/path/to/image.jpg" />
    {/* Other elements: A Headline and a subtitle for example */}
  </div>

Isn't there a better approach?

Well, at first, I thought of making use of Next.js's _document.tsx and just adding a native script tag referencing the original asset file inside it in an old-fashioned way. But that will leave the precious optimization feature of Next.Js aside since the Script component itself does not work well for the template's assets.

I could have also made a component acting as a container that accepts an 'src' prop for the image background and passes the inner elements as children. Using that component would have looked like this:

<BgImage
  as="div" // or any other element tag identifier
  src="/path/to/image.jpg"
  className="" // CSS classes of the container element 
>
  {/* No need for an image element anymore here */}
-  <img class="bg-img" src="/path/to/image.jpg" />
  {/* Other elements: A Headline and a subtitle for example */}
</BgImage>

But I think it's diverging from the original template's elements structure. The first solution is better in my opinion as it only requires me to replace the img element from the template to ImageToBg component directly. But that's only my opinion.

What do you think of it?

PS: if you are reading this article from my main blog, you can interact with some of my articles on the Hasnode blog instead for now, as I am planning to add a comment section in the future.