DEV Community

SREY
SREY

Posted on

How to Preview Images before Upload in React.js with textarea dynamically increasing Modal's height ♥

Hello Folks on the internet, mostly from last few days I was revolving around react-query but now tbh, I was not getting enough escape from my task and at night I use to play valorant to release some dopamine and feel pleasurable, Today Morning I was contributing to my own product which I am building from scratch and I found something very noticable their are many blogs on Drag and Drop API but there are few on building the image selection and preview it from scratch, so here I got the idea of making a great one using TS. (By the way I have seen lot of it lately, People are moving from TS to JS especially the contributors to library. With no further Ado Lets Jump in Straight towards the blog.


Lets start with some basic jsx code to render a modal which takes some values as input and also a image button to add a image which we can preview.

   <div className='parent_modal_input_container'>
      <div className='input-container'>
        <textarea
          maxLength={250}
          onChange={handleChange}
          placeholder='Random Thoughts? :/ Enter here '
          className='input_text_area'
          rows={textareaRows}
          autoFocus={true}
         />
       </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Okay in the above piece of code we create a basic jsx to render on the page where i guess there is some parent element with div tag and also a textarea with a maximum length of 250 characters and also added rows to make the card or i might say parent element grow it size with the number of rows increased. so there I took a state to keep a track of rows i.e. with textareaRows; also apart from that I have added the onChange property when users starts typing we incur with a handleChange function.

As we added states for two things one to keep a track of textareaRows and the other is the content as the user types here is the code for that...

 const [textContent, setTextContent] = useState<string>("")
 const [textareaRows, setTextareaRows] = useState<number>(1);
 const [selectedFiles, setSelectedFiles] = useState<File[] | null>(); // let us discuss this later.

Enter fullscreen mode Exit fullscreen mode

Moreover as we start towards the image preview and upload functionality we need to create the button and input where it have file type which accepts image/* that is image with all the formats of its jpg, jpeg, png etc. so here we define the input tag below wrapped inside a button (before you find css in the entire blog I must say, I didnt add css cause i dont want to make a blog too big keep it very cut to short with essential parts! )

Here is the input tag with the same stuff we talked about

  <button>
    <label>
      <input type='file' accept="image/*" className='hidden'  
        multiple={false} onChange={(e) => uploadFile(e)} />
         <ImageIcon width={25} height={25} />
      </label>
  </button>

Enter fullscreen mode Exit fullscreen mode

Here I have used svg's with the most specific thing that is svg's as ReactComponent and it requires very special addition to typescript and if in specific you are adding a vite bundler to bundle things up.

Let start with some basic steps towards our functionality and add handleChange function,

const textareaLineHeight = 24; // Adjust this value according to your styling
    const minRows = 1;
    const maxRows = 5; // Adjust as needed

    const previousRows = e.target.rows;
    e.target.rows = minRows; // Reset the rows to the minimum

    const currentRows = Math.floor((e.target.scrollHeight - textareaLineHeight) / textareaLineHeight) + 1;

    if (currentRows === previousRows) {
      e.target.rows = currentRows;
    }

    if (currentRows >= maxRows) {
      e.target.rows = maxRows;
      e.target.scrollTop = e.target.scrollHeight;
    }

    setTextContent(e.target.value);
    setTextareaRows(currentRows);
Enter fullscreen mode Exit fullscreen mode

hence in the above code we added a minimum rows up to 1 and maximum rows upto 5 with a variable defined previous rows , current rows, previous rows is nothing but the event.target.rows which is defined by the dom event parameter. and current rows is the floor value of (e.target.scrollHeight - textareaLineHeight)/ textareaLineHeight why this cause this is the portion where we add the stuff for dynamically increasing the parents size when the row size increases by defining the current row hence if we say that the currentRow = 1 it means the scroll height would be 24 and hence [{(24-24)/24} + 1].

hence if the text overflows and starts to the next line the value of the current rows increases and becomes 2 with the scroll height with value 48 hence it all revolves around the initial value of textareaLineHeight.

Moreover if you see there is an unusal behaviour it oscillates between the rows that is current and previous rows why does this happens is very clear as we are defining this stuff inside the onChange parameter hence to make sure it works smooth upto some extent is our target and we can do it by a very special and beloved stuff a debounce function by lodash ( why lodash my project is already have lodash so i might use it but to build it from sratch is very simple here you go with this link but I might say what does debounce do, it delays the function with the milliseconds as argument hence upto that millisecond it will delay the function this will make something good to us I promise :)

const debouncedHandleChange = debounce((e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const textareaLineHeight = 24; // Adjust this value according to your styling
    const minRows = 1;
    const maxRows = 5; // Adjust as needed

    const previousRows = e.target.rows;
    e.target.rows = minRows; // Reset the rows to the minimum

    const currentRows = Math.floor((e.target.scrollHeight - textareaLineHeight) / textareaLineHeight) + 1;

    if (currentRows === previousRows) {
      e.target.rows = currentRows;
    }

    if (currentRows >= maxRows) {
      e.target.rows = maxRows;
      e.target.scrollTop = e.target.scrollHeight;
    }

    setTextContent(e.target.value);
    setTextareaRows(currentRows);
  }, 130);

  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    debouncedHandleChange(e);
  };

Enter fullscreen mode Exit fullscreen mode

Remember we discussed the stuff before while declaring state we left one of it there the selectedFile stuff that is used to keep track for the files when the input is given that is when the button is clicked the upload function runs

  const uploadFile = (e : 
   React.ChangeEvent<HTMLInputElement>) => {
    if(!e.target.files || e.target.files.length === 0){
      setSelectedFiles(undefined);
    }
    if(e.target.files){
      setSelectedFiles([e.target.files[0]])
    }    
} 

Enter fullscreen mode Exit fullscreen mode

so what we did above is nothing we made a check for if the target.files exist and its length should be greater than 0;
if it is then we use the setter method in our case our setterState dispaches and it sets the e.target.files[0] why 0 make sure you do a console.log you will get to know :)

lets print it out i mean render it out

<div className='parent_modal_input_container'>
   <div className='input-container'>
     <textarea
       maxLength={250}
       onChange={handleChange}
       placeholder='Random Thoughts? :/ Enter here '
       className='input_text_area'
       rows={textareaRows}
       autoFocus={true}
      />
    </div>
 </div>
 {
   selectedFiles && selectedFiles.map((file, key) => {
    return (
     <div key={key} className="row mt-2 relative">
       <span className="filename">
         <img className='rounded-md h-[150px] md:w-[500px] md:h- 
            [400px] m-auto' src={URL.createObjectURL(file)} alt= 
            {file.name} />
         </span>
      </div>
     )
   })
  }

Enter fullscreen mode Exit fullscreen mode

So whats left, I dont think so much is left we also need to remove the image if some one wants to make a new image selected from the input field that we defined, so hence for doing that we might handle it using making selectedFiles set to undefined so this will remove it from the selectedFiles state contianing array.

Okay that is not something major, but we will make it more interesting defining it

{
   selectedFiles && selectedFiles.map((file, key) => {
    return (
     <div key={key} className="row mt-2 relative">
       <span className="filename">
         <img className='rounded-md h-[150px] md:w-[500px] md:h- 
            [400px] m-auto' src={URL.createObjectURL(file)} alt= 
            {file.name} />
         </span>
         <section>
           <button onClick={() => removeFile([file])}><CloseIcon 
             className='absolute top-0 right-[20%] md:right-[5%] 
             h-[22px] w-[22px] md:h-[30px] md:w-[30px]' />
            </button>
         </section>
      </div>

     )
   })
  }

// here is the function :()

  const handleRemovePhotoFile = (selected: File[]) => {
    const filteredFileData = selectedFiles?.filter(
     (_file) => _file !== selected[0]);
    setSelectedFiles(filteredFileData);
  };

Enter fullscreen mode Exit fullscreen mode

To be very honest their is one more bug if you followed along up till now you might have known it only allows you to get the image selected for the first time but after you remove it , it dont (for me i had only one image on my system so it didn't work hence i got a way after i passed in something wanna take a guess I spent completley more than 45 mins thinking what is wrong hence just add something to our input of file like this

<input type='file' accept="image/*" className='hidden' multiple={false} onChange={(e) => uploadFile(e)} value={""} />

Enter fullscreen mode Exit fullscreen mode

You Got it Right ??

The value parameter we forgot to add, oh silly me but yeah I got to over-think that i should reset e.target.files or make it null, but finally as a fraction of my attention went to input tag and asked where is value that is initially defined and here i got it so that's why i say I write code and mostly it is shitty 🃏!

But there is one more thing left if you see and fix it please comment it down cause I already have fixed it If you can find it Please just let the people know how cool are you finding the bugs!


So that's all if you have reached till here you might have liked it and i might have added some value to you if it is so please drop a like and save I guess it will make my day!

That's all folks I might be a shitty coder But I am great debugger Haha Just Kidding I do worst at both Btw have a look at my product as well Kinda making my life harder working at it but i love it though

Thats all Have a great week ahead ! GoodBye Fellas!

Top comments (0)