DEV Community

Cover image for Asset(Image/Video) Picker with javascript(react/nextjs) on button click
Swahili Speaking Coder
Swahili Speaking Coder

Posted on

Asset(Image/Video) Picker with javascript(react/nextjs) on button click

Before we start i am going to put my social media links if you would like to follow me there
Instagram
Twitter

and also my upwork profile if you want to work with me

In this blog post i am going to demonstrate how to create an asset picker on button click.
Below is how the the ui of the picker looks like and how it works

First of all we are going to create our app with next js a react framework

npx create-next-app assetpicker
Enter fullscreen mode Exit fullscreen mode

make sure all the characters are in small letters because next js does not allow capital letters
and during the creation of the app select yes for typescript on the terminal pop up because we are going to use typescript in this app
After app creation we are going to install 3 npm packages which are react-icons, classnames and sass

npm i react-icons classnames sass
Enter fullscreen mode Exit fullscreen mode

After installation go to pages and delete index.tsx content but not the file itself and we are going to start from scratch

we are going to have 4 items in our setState which are image, selecta, current and names

 const [image, setImage] = useState<any>([])
 const [selecta, setSelecta] = useState<any>([])
 const [current, setCurrent] = useState(0)
 const [names, setNames] = useState<any>([])
Enter fullscreen mode Exit fullscreen mode

image this is the actual image **which is going to be displayed inside a div known as **selecta and current is the current selected image which by default is the first one and names contains an array of names of all selected images this is going to help us to check if the image is already selected or not

Apart from those we are going to have 3 functions addSelecta, onChange and deleteData

 const addSelecta = () => {
    let image1 = selecta.length
    if(selecta.length > image.length){
      alert('not allowed')
      return 
    }else if( selecta.length >= 5){
      alert('maximum images 5 reached')
      return 
    }else{
      selecta.push(`image${image1+1}`)
      setSelecta(selecta.map((item:any)=> item))
    }
  }
Enter fullscreen mode Exit fullscreen mode

in this function we are adding a button to select image and that div is going to display a plus icon if there is no image selected and and image otherwise.
Every Time we click a button we are going to add an item named image plus selecta.length icreament so selecta item is going to be image0, image1 etc in an array

 const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const one:any = e.target.files
    const two = URL.createObjectURL(one[0])
    if(names.includes(one[0].name)){
      alert('asset available')
      return 
    }else{
      image.push(two)
      names.push(one[0].name)
      setImage(image.map((item:any)=> item))
      setNames(names.map((item:any)=> item))
    }
  }
Enter fullscreen mode Exit fullscreen mode

in in onchange function this is all about selecting assets but there is more to that.
we are going to have a constant value named one **which is simply an array assignment of assets.
and the second constant value is **two
which is simply a url creation for selected files number one.
we are using URL.createObjectURL(one[0]) because we cannot display files as files files on the web unless they are BLOB so this method is simply a creation of a BLOB url

const deleteData = (image1:any, selecta1:any) => {
    const imageIndex = image.indexOf(image1)
    const selectaIndex = selecta.indexOf(selecta1)

    if(selecta.includes(selecta1)){
      selecta.splice(selectaIndex,1)
      image.splice(imageIndex,1)
      names.splice(image1.name,1)
      setSelecta(selecta.map((item:any)=> item))
      setImage(image.map((item:any)=> item))
      setNames(names.map((item:any)=> item))
    }
  }
Enter fullscreen mode Exit fullscreen mode

The third function deleteData we are going to pass 2 items along with it the first one is *image **which is the image index of that selected image plus the selecta index of the selecta array.
Then we are going to get indexes of those two **imageIndex **and **selectaIndex *

and then we are going to check if the selecta array includes the selected selecta if true we are going to delete the selecta, image and name and after that we are going to set them with new array so as to update the ui because if we do not do that the changes will not be reflected on the ui

return (
    <div className={styles.home}>
      <div className={styles.item}>
        <div className={styles.hero}>
          <div className={styles.thumbs}>
            {
              image.map((item:any, index:number)=> (
                <div className={cx(styles.dot, current===index && styles.active)} onClick={()=> setCurrent(index)}></div>
              ))
            }
          </div>
          {
            image.length < 1
            ? null
            : <img src={image[current]} alt="" />
          }
        </div>
        <div className={styles.action}>
          <div className={styles.left}>
            {
              selecta.map((item:any, index:number)=> (
                <div className={styles.item}>
                  {
                    index+1 > image.length
                    ?<div className={styles.picker}>
                      <input type="file"
                      id='file'
                      style={{display:'none'}} 
                      onChange={e => onChange(e)}
                      />
                      <label htmlFor="file">
                        <BiPlus className={styles.icon} size={25}/>
                      </label>
                    </div>
                    : <img src={image[index]} alt=""  onClick={()=> deleteData(image[index], item)}/>
                  }
                </div>
              ))
            }
          </div>
          <div className={styles.right}>
            <div className={styles.add} onClick={() => addSelecta()}>
              <BiPlus className={styles.icon}/>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
Enter fullscreen mode Exit fullscreen mode

After all that we need to write our code we are going to have home as our main div and inside it there is item and inside item we are having hero section which is the big image.

 <div className={styles.hero}>
          <div className={styles.thumbs}>
            {
              image.map((item:any, index:number)=> (
                <div className={cx(styles.dot, current===index && styles.active)} onClick={()=> setCurrent(index)}></div>
              ))
            }
          </div>
          {
            image.length < 1
            ? null
            : <img src={image[current]} alt="" />
          }
        </div>
Enter fullscreen mode Exit fullscreen mode

Inside the hero section we have thumbs which are the small dots indicating the selected image and unselected ones.

and then we have the action div with left and right divs the left is an array of selectas and the right one is the button to add selectas.
inside the selecta we are going to check if selecta has an image or not if not we are going to show a button to select the image

here is the full code

Index.tsx

import React, { useState } from 'react'
import styles from './home.module.scss'
import { BiPlus } from 'react-icons/bi'
import cx from 'classnames'

function Home() {
  const [image, setImage] = useState<any>([])
  const [selecta, setSelecta] = useState<any>([])
  const [current, setCurrent] = useState(0)
  const [names, setNames] = useState<any>([])

  const addSelecta = () => {
    let image1 = selecta.length
    if(selecta.length > image.length){
      alert('not allowed')
      return 
    }else if( selecta.length >= 5){
      alert('maximum images 5 reached')
      return 
    }else{
      selecta.push(`image${image1+1}`)
      setSelecta(selecta.map((item:any)=> item))
    }
  }

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const one:any = e.target.files
    const two = URL.createObjectURL(one[0])
    if(names.includes(one[0].name)){
      alert('asset available')
      return 
    }else{
      image.push(two)
      names.push(one[0].name)
      setImage(image.map((item:any)=> item))
      setNames(names.map((item:any)=> item))
    }
  }

  const deleteData = (image1:any, selecta1:any) => {
    const imageIndex = image.indexOf(image1)
    const selectaIndex = selecta.indexOf(selecta1)

    if(selecta.includes(selecta1)){
      selecta.splice(selectaIndex,1)
      image.splice(imageIndex,1)
      names.splice(image1.name,1)
      setSelecta(selecta.map((item:any)=> item))
      setImage(image.map((item:any)=> item))
      setNames(names.map((item:any)=> item))
    }
  }
  return (
    <div className={styles.home}>
      <div className={styles.item}>
        <div className={styles.hero}>
          <div className={styles.thumbs}>
            {
              image.map((item:any, index:number)=> (
                <div className={cx(styles.dot, current===index && styles.active)} onClick={()=> setCurrent(index)}></div>
              ))
            }
          </div>
          {
            image.length < 1
            ? null
            : <img src={image[current]} alt="" />
          }
        </div>
        <div className={styles.action}>
          <div className={styles.left}>
            {
              selecta.map((item:any, index:number)=> (
                <div className={styles.item}>
                  {
                    index+1 > image.length
                    ?<div className={styles.picker}>
                      <input type="file"
                      id='file'
                      style={{display:'none'}} 
                      onChange={e => onChange(e)}
                      />
                      <label htmlFor="file">
                        <BiPlus className={styles.icon} size={25}/>
                      </label>
                    </div>
                    : <img src={image[index]} alt=""  onClick={()=> deleteData(image[index], item)}/>
                  }
                </div>
              ))
            }
          </div>
          <div className={styles.right}>
            <div className={styles.add} onClick={() => addSelecta()}>
              <BiPlus className={styles.icon}/>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

export default Home
Enter fullscreen mode Exit fullscreen mode

home.module.scss

.home{
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;

    .item{
        width: 50%;

        .hero{
            width: 100%;
            height: 400px;
            background: black;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;

            img{
                width: 90%;
                height: 90%;
                object-fit: cover;
            }
            .thumbs{
                position: absolute;
                bottom: 0;
                display: flex;
                align-items: center;
                justify-content: center;

                .dot{
                    width: 10px;
                    height: 10px;
                    margin: 5px;
                    background: white;
                    cursor: pointer;

                    &.active{
                        background: crimson;
                    }
                }
            }
        }

        .action{
            width: 100%;
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin: 10px 0;

            .left{
                width: 90%;
                display: flex;
                align-items: center;
                justify-content: flex-start;

                .item{
                    width: 100px;
                    height: 100px;
                    background: crimson;
                    margin-right: 10px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: pointer;

                    img{
                        width: 100%;
                        height: 100%;
                        object-fit: cover;
                    }
                }
            }
            .right{
                width: 10%;

                .add{
                    background: crimson;
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    width: 40px;
                    height: 40px;
                    cursor: pointer;

                    .icon{
                        color: white;
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Latest comments (0)