REACT 2, Input props and onChange(), React form onSubmit() and formData, input multiple attribute an

React Input Props and onChange() event handler

ReactJs input props allow us to have controlled inputs, in which we set an input's value using useState() and can edit it using an onChange() event handler.

We re-set the value of the useState() based on the input current e.target.value (target being a property of the event interface, referencing the input).

const [costo, setCosto] = useState(0)

<div>
  <label htmlFor="cash" className="form-label">How much spending?</label>
  <input id="cash" type="range" className="form-range" value={costo} 
         onChange={(e)=> setCosto(e.target.value)} name="costo" />
  <p className="text-center">Price being: {costo}$</p>
</div>
Changing input value gets rendered with useState()
useState() with number input

When updating an integer useState() outside the input we need to convert it with Number(), to maintain the single useState().

//while on the input onChange() is teh same as before

<input type="number" className="form-input" value={numba}
       onChange={(e)=> setNumba(e.target.value)} />
<button type="button" className="btn btn-sm btn-secondary" 
        onClick={()=> setNumba((x)=> Number(x) + 10) }>
  Add 10
</button>
Input number with Onchange

Text and radio/checkbox inputs use defaultValue/checked for the default value prop, if the checkbox doesn't have a value prop on submit its value will be "on".

//We use an useState() to set a defaultChecked(works for value too)
const [lenguage, setLenguage] = useState( "Hindi" );

<div className="form-check">
  <input type="radio" name='language' defaultChecked={true} value="hindi"
         onChange={(e)=> setLenguage(e.target.value) } />
  <label >Hindi</label>
</div>
<div className="form-check">
  <input type="radio" name='language' value="spanish"
         onChange={(e)=> setLenguage(e.target.value) } />
  <label>Spanish</label>
</div>
Defaultchecker() value set

We use the useId() React hook to avoid conflicts when re-rendering multiple labels/inputs.

//We can add strings to the useId() to avoid callin it multiple times

const testo= useId()

<form className="row">
  <div className="col-6">
    <input id={testo} type="text" className="form-control" 
           defaultValue="any useState()" />
  </div

  <div className="col-6">
    <div className="form-check">
      <label htmlFor={testo + "check"} className="form-check-label">checked</label>
      <input id={test + "check"} type="checkbox" checked={true}  
      value="any useState()" className="form-check-input" />
    </div>
  </div>

  <div className="col-4">
    <button className="btn btn-primary">Submit</button>
  </div>
</form>
defaultValue text and checked checkbox

We use the max/min input prop to set its submit conditions, the event handler onInvalid() is triggered when the submit fails.

//For text input we use min/manLenght

<form>
  <input type="text" className="form-control" maxLength="10" minLength="4"
         onInvalid={()=> {console.log("riprova")}}/>

  <div>
    <label htmlFor={testo} className="form-label">Numeri</label>
    <input id={testo} type="number" className="form-control" min="120" max="400" />
  </div>

  <button className="btn btn-primary">Submit</button>
</form>
The error messae on failed submit is included

React Form onSubmit() event handler and FormData()

A type="submit" button will trigger the form onSubmit() event handler.

//You can use type="reset" to reset the form or type="button" to not form submit
const [mino, setMino] = useState("")

<form onSubmit={invia}>
  <input type="text" className="form-control" name="elemento" 
         value={mino} onChange={(e)=> setMino(e.target.value)} />

  <input type="checkbox" name="checked" className="form-check-input" />

  <button type="submit" className="btn btn-primary">Submit</button>
</form>

We then use new FormData(form, submitter) interface to render the e.target HTML form from onSubmit(), as a name/value iterable.

We finally use the Object.fromEntries(iterable) static method to convert the FormData into an Object.

//The keys/names are gonna be in alphabetical order
//If the checkbox is not selected its name won't appear in the formData
function manda(e){
  e.preventDefault()

  let forma= e.target
  let formdata = new FormData(forma)
  let dati = Object.fromEntries(formdata.entries())

  console.log(dati)
}
form onSubmit() and formData object

We use FormData methods to edit the form object.

We can use append(key, value) and set(key, value) to add a new key/value pair, delete(key) to remove one, get(key) to return the first value associated with the key, and has(key) which returns true/false depending if the key is present.

//Values will be converted in strings (like "true" and "96")
let form= e.target
let formdata= new FormData(form)

formdata.append("nuovo", 101)
formdata.set("vecchio", "penny")

console.log( formdata.get("nuovo") )    //"101"
formdata.delete("nuovo")  
console.log( formdata.has("nuovo") )   //"false"

We can use the entries(), keys(), and values() methods to return a key/values, keys, and values iterable.

//In order to get the values we need to loop the iterable
for (const pair of formdata.entries()) {
  console.log( pair );          //["checked", "on"], ["elemento","messo"]
}

formdata.values()               //checked, elemento
formdata.keys()                 //on, messo

Input file image render and multiple input attribute

On type="file" <input> we use the accept property which sets the MIME file types we can submit. We can use accept= "audio/*" and "image/*" and "video/*" in case we want to accept any file type.

//With multiple accept properties we use (,), we use the formdata for image src

<div className="col-3">
  <form onSubmit={immagine} className="text-center">
    <div className="mb-3">
      <label htmlFor="nsa" className="form-label"></label>
      <input id="nsa" type="file" className="form-control" 
             name="sauce" accept=".png, .webp"/>
    </div>

    <button type="submit" className="btn btn-primary">Submit</button>
  </form>
</div>

<div className="col-3" >
  <img src={vedere} className="img-fluid" />
  <p> {digitale} </p>
</div>
form-control file input and image submit

Its returned formdata will be an object of image properties, the lastModified (the UNIX epoc number) in milliseconds, its size in bytes, the file's name, and its MIME type.

Input image file formdata

The accept property isn't enough to filter the input files so we use a server-side function.

//We post the image only if its file type is Included in out array

const fileTypes = [
  'image/apng',
  'image/bmp',
  'image/gif',
  'image/jpeg',
  'image/pjpeg',
  'image/png',
  'image/svg+xml',
  'image/tiff',
  'image/webp',
  `image/x-icon`
];

function tipo(poster){
  return fileTypes.includes(poster.type)
}

The Byte is the basic unit of file storage, based on the binary system the Kilobyte will be 1024 bytes (2^10) while the Megabyte is 1024^2.

//we convert the image file size 

function grande(size){
  if(size < 1024) {
    return size + 'bytes';
  } else if(size > 1024 && size < 1048576) {
    return (size/1024).toFixed(1) + 'KB';
  } else if(size > 1048576) {
    return (size/1048576).toFixed(1) + 'MB';
  }
}

To render the image src we need to URL.createObjectURL() the entire image object.

//We useState() to render the src in the DOM

function immagine(e){
  e.preventDefault()

  let form= e.target
  let formdata= new FormData(form)
  let result= Object.fromEntries(formdata.entries())

  if(tipo(result.sauce)){
    let abb = grande(result.sauce.size)
    setDigitale("The file size is " + abb )
    setVedere( URL.createObjectURL(result.sauce) )
  }
}
Image formdata properties

The multiple input attribute allows submitting of multiple values in a single input.

If used on a type="text" input, the values need to be separated with a comma (,), any space will be cut in the formdata value.

If added to <select> it will expand its size, we will use ctrl + click to multiple select on pc (while we get a pop-up on mobile)

Multiple attribute <input> and Javascript expression render

We can limit the size of an <select> after we add multiple.

//we use the useId() hook to create id for the input/labels

let base = useId()

<form onSubmit={manda}>
  <div className="my-3">
    <label htmlFor={base+"one"} className="form-label">Multi email</label>
    <input id={base+"one"} type="email" name="posta" 
            className="form-control" multiple />
  </div>

  <div>
    <label htmlFor={base+"tre"} className="form-label">Multi files</label>
    <input id={base + "base"} type="file" className="form-control" 
            name="imma" accept="image/*" multiple/>
  </div>

  <div className="my-3">
    <label htmlFor={base+"due"} className="form-label">Multi select</label>
    <select id={base + "due"} name="insieme" className="form-select" 
            size="3" multiple>
      <option value="primo">uno</option>
      <option value="secondo">due</option>
      <option value="terzo">tre</option>
      <option value="quarto">quattro</option>
      <option value="quinto">cinque</option>
      <option value="sei">sei</option>
    </select>
  </div>

  <button className="btn btn-primary">submit</button>
</form>

We will render the javascript expressions after the formdata is submitted.

//they are not components tho, they are useState() with node elements

<div className="col-7">
  <div className="row text-center">
      <h3> Images sent to: {mail} </h3>
  </div>

  <div className="row align-items-center">
    {gruppo}
  </div>

  <div className="row my-2">
    <p> The selected values: {choice}</p>
  </div>
</div>
multiple input submit and render
Rendering formdata entries for multiple <input>

The formdata object from a multiple <input> will only show the LAST value of each name/key but the formdata iterable will keep track of every multiple input value.

We use an useState([]) to render an array of React node elements as a javascript expression {}.

We can't use push() for the useState() because it returns the array's length, not the edited array, while concat() returns the "new" array.

//The image property value is an object so we need {} to deconstruct it
//We use array deconstruct in the setter useState() function
//The multiple type="text" input formdata will return all the values at once

const [gruppo, setGruppo] = useState( [] )
const [ mail, setMail ] = useState("")
const [ choice, setChoice ] =useState([])

function manda(e){
  e.preventDefault()

  let form= e.target
  let formdata= new FormData(form)
  
  for(const sor of formdata.entries()){
    if( sor[0] == "imma" ){
    
      const {size, name, lastModified, type} = sor[1]

      setGruppo( gru => [
        ...gru, 
        <div className="col-4" key={lastModified} >
          <img src={URL.createObjectURL(sor[1])} className="img-fluid" />
        </div>
      ])
    }else if( sor[0] == "insieme" ){

      setChoice( mal => [
        ...mal,
        <span key={sor[1]}>
          {sor[1] + " "}
        </span>
       ]
      )
    }
  }

  let formula= Object.fromEntries(formdata.entries())

  let uno = formula.posta.split(",")

  for( const sent of uno ){
    setMail(item => item + sent + ", " )
  }
}
multiple input formdata object

Last updated

Was this helpful?