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

image to background react component
By Andrianarisoa Daniel

The context

For developping 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 an utility CSS class named 'bg-img':

index.html
<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:

imageToBackground.js
$(".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 in the traditional way into the html document right before the closing body tag. Basically what it tells the browser to do is to get the image's (with the 'bg-img' class) src attribute, and set it as background image of it's 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
// 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:

index.tsx
  <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 add a native script tag referencing to the original asset file inside it like in the 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 which accepts a 'src' prop for the image background and pass the the inner elements as children. Using that component would have looked like this:

index.tsx
<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 require me to replace the img element from the template to ImageToBg component directly. But that's only my opininon.

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.