DEV Community

Yosra Skhiri
Yosra Skhiri

Posted on

Make an image preview in React js

It is a cool UI interaction when apps show a preview of the added image to the user before they upload it to the server. This post explains how to achieve that using React js.

We're going to use a functional component called ImgPrev.js, the placeholder image is needed to be (you guessed it) a placeholder for the img tag, we also need to import the CSS style file to complete the magic.
This is the photo I used as a placeholder (If you can't see it, it's because the photo is white, but trust me it's there! 😄).

placeholder

import React, { useState } from 'react';
import placeholder from '../images/placeholder.png';
import './style.css';

const ImgPrev = () => {
   return(
      <form></form>
   );
}

export default ImgPrev;
Enter fullscreen mode Exit fullscreen mode

Now let's talk a little bit about that stylesheet.

/* This is used to reset the default styles 
applied to every element in this app */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

.form__title {
    margin: 32px 0;
    text-align: center;
    color: #002952;
}

.form__img-input-container {
    position: relative;
    width: 300px;
    height: 300px;
    margin: auto;
}

/* this class is used to hide the file input */
.visually-hidden {
    position: absolute;
    height: 1px;
    width: 1px;
    overflow: hidden;
    clip: rect(1px, 1px, 1px, 1px);
}

/* Adding the focus and focus-within styles for accessibility */
input.visually-hidden:focus + label,
input.visually-hidden:focus-within + label {
    outline: 8px solid rgba(86, 206, 239, .4);
}

/* The file input is a labeled control, and since we hid it, 
we're going to use its associated label to trigger it */
.form-img__file-label {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 300px;
    height: 300px;
    border-radius: 50%;
    cursor: pointer;
    background-color: rgba(245, 245, 231, .3);
}

.form-img__file-label:hover {
    background-color: rgba(245, 245, 231, .5);
}

.form-img__file-label > svg {
    position: absolute;
    top: 50%;
    left: 50%;
    opacity: .7;
    transform: translate(-50%, -50%);
}

.form-img__file-label:hover > svg {
    opacity: 1;
}

/* The image element is going to be positioned under the 
label element, since the label is a see through, we're going 
to be able to see the preview of the image. */
.form-img__img-preview {
    display: block;
    width: 300px;
    height: 300px;
    object-fit: contain;
    border-radius: 50%;
    border: 16px rgb(80, 199, 235) solid;
}
Enter fullscreen mode Exit fullscreen mode

To hide the file input I used the style rules mentioned in this MDN page.

Our component is going to return some JSX code that includes the file input wich will accept only these types of files: png, jpg and jpeg, as soon as the user clicks on the label and chooses (or cancels) an image, the handleImg method will fire, and the img tag will display (or not) the chosen image by the user.

return (
        <form encType="multipart/form-data">
            <h1 className="form__title">Image Preview in Reactjs</h1>
            <div className="form__img-input-container">
                <input 
                    type="file" 
                    accept=".png, .jpg, .jpeg" 
                    id="photo" 
                    className="visually-hidden"
                    onChange={handleImg}

                />
                <label htmlFor="photo" className="form-img__file-label">
                    <svg width="150" height="150" viewBox="0 0 24 24" fill="none" stroke="#56ceef" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M5.52 19c.64-2.2 1.84-3 3.22-3h6.52c1.38 0 2.58.8 3.22 3" />
                        <circle cx="12" cy="10" r="3" />
                        <circle cx="12" cy="12" r="10" />
                    </svg>
                </label>
                <img src={src} alt={alt} className="form-img__img-preview"/>
            </div>
        </form>
    );
Enter fullscreen mode Exit fullscreen mode

To make sure that our image is being displayed we need to use a state to store it and this will ensure that in every state update the component will be re-rendered and every time that happens, the component will preview the chosen image.
Our state stores the src and the alt attributes of the image tag and has the placeholder and 'Upload an Image' as the default values.

const [{alt, src}, setImg] = useState({
        src: placeholder,
        alt: 'Upload an Image'
});
Enter fullscreen mode Exit fullscreen mode

In the handleImg method, we need to check first if the user chose an image or not, if we don't, this error will appear TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed..
inside the if statement, the state is being updated by creating a URL that points to the image we're going to preview, we're also updating the alt attribute using the image's name.

Finally, this is the whole component:

import React, { useState } from 'react';
import placeholder from '../images/placeholder.png';
import './style.css';

const ImgPrev = () => {
    const [{alt, src}, setImg] = useState({
        src: placeholder,
        alt: 'Upload an Image'
    });

    const handleImg = (e) => {
        if(e.target.files[0]) {
            setImg({
                src: URL.createObjectURL(e.target.files[0]),
                alt: e.target.files[0].name
            });    
        }   
    }

    return (
        <form encType="multipart/form-data">
            <h1 className="form__title">Image Preview in Reactjs</h1>
            <div className="form__img-input-container">
                <input 
                    type="file" 
                    accept=".png, .jpg, .jpeg" 
                    id="photo" 
                    className="visually-hidden"
                    onChange={handleImg}
                />
                <label htmlFor="photo" className="form-img__file-label">
                    <svg width="150" height="150" viewBox="0 0 24 24" fill="none" stroke="#56ceef" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M5.52 19c.64-2.2 1.84-3 3.22-3h6.52c1.38 0 2.58.8 3.22 3" />
                        <circle cx="12" cy="10" r="3" />
                        <circle cx="12" cy="12" r="10" />
                    </svg>
                </label>
                <img src={src} alt={alt} className="form-img__img-preview"/>
            </div>
        </form>
    );
}

export default ImgPrev;
Enter fullscreen mode Exit fullscreen mode

And this is what it would look like in the browser:
Screenshot app

Top comments (2)

Collapse
 
jixboa profile image
Kwame

Great!!

Collapse
 
mahsumkocabey profile image
Mahsum Kocabey

This is great, I really liked the UI. But any idea how I can add multiple images??

Thanks