Wyszukaj / o blogu

React: formularze

Opublikowano wto 25 sierpnia 2020 w reactjs • 5 min read

react

W kontekście Reacta można stosować dwa rodzaje formularzy - tzw. kontorolowane i niekontorlowane

Formularze niekontorlowane to są te, których logika nie jest sterowana przy pomocy JS/Reacta innymi słowy, których dane nie są przechwytywane przez stworzone komponenty aplikacji -> przeciwieństwem tego podejścia są formularze kontrolowane

Stany ponad wszystko -> stan jako “wyłączne źródło prawdy”

Różnego rodzaju elementy formularzy takie jak <input> <textarea> <select> w HTML posiadają własny stan, który jest aktualizowany na podstawie informacji wpisywanych przez użytkownika w recatowych formularzach kontrolowanych ten stan jest przechwytywany i skorelowany ze stanem danego komponentu np. poprzez metodę setState lub inną przypisaną do zmiany stanu komponentu w przypadku komponentów funkcyjnych (stan może być zapisany jako obiekt i zbierać informację z wielu formularzy lub każdy z nich może mieć osobny stan).

Prosty przykład gdzie 1 funkcja obsługuje jeden element formularza

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
  }

  handleChange = (event) => {    
    this.setState({value: event.target.value});
    console.log(this.state.value) // wartość stanu jest stale aktualizowana
  }

  render() {
    return (
      <form> 
        <input type="text" value={this.state.value} onChange={this.handleChange}/>
      </form>
    );
  }
}

Przykład gdzie jedna funkcja obsługuje wiele elementów formularza

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      surname: '';
      };
  }

  // funkcja wykorzystująca event i przypisująca dane do konkretnego elementu stanu
   o tej samej nazwie gdzie name === jak element, do którego wartość ma być przypisana
  handleChange = (event) => {    
    this.setState({
      [e.target.name]: event.target.value
    });
  }

  // funkcja zajmująca się przesłaniem danych
  handleSubmit(event) {
    alert('Podano następujące imię: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="text" name='name' value={this.state.name} onChange={this.handleChange}/>
        <input type="text" name='surname' value={this.state.surname} onChange={this.handleChange}/>
        <input type="submit" value="Send"/>
      <form>

    );
  }
}

W przypadku komponentów funkcyjnych możemy daną funkcję odpowiedzialną za zmianę stanu komponentu wpisać inlinowo

const Form = () => {
  const [name, setName] = useState("");

return (
  <form>
    <input type="text" value={name} onChange={e => setName(e.target.value)}/>
  </form>
  );
};

W przypadku stworzenia jednego stanu dla wielu elementów formularza należy pamiętać o tym, że będzie musiał być on w całości przywołany - można do tego wkorzystać operatora rozpraszającego (destrukturyzacji)

const Form = () => {
  const [form, setForm] = useState({
    name: '',
    surname: ''
  });

const handleChange = (e) => {
  const {name, value} = e.target; // destrukturyzacja obiektu e.target na imię i wartość
  setForm(prevState => { // wykorzystanie poprzedniego stanu do jego modyfikacji
    return {    // zwrócenie obiektu
      ...prevState,
      [name]: value // wprowadzenie nowej wartości
      }
  });
};

return (
  <form>
    <input type="text" name='name' value={form.name} onChange={handleChange}/>
    <input type="text" name='surname' value={form.surname} onChange={handleChange}/>
  </form>
  );
};

Custom hook dla formularza

Przykładowe użycie wygląda w następujący sposóbu W komponencie wykorzystującym custom hook przy pomocy destrukturyzacji należy go zaimportować, użyć a następnie wyspredować lub wypisać

// useInput.js
import {useState} from "react";
export default (valueOnStart) => {
  const [value, setValue] = useState(valueOnStart);

  return [
    value, // zwraca nazwę stanu i jest odpowiednikiem [name]: value 
    {   // tablica
      value,  // stan pola
      onChange: e => { // gdy wykryje zmianę aktualizuje stan
        setValue(e.target.value);
      }
    }
  ]
};

import React from "react";
import useInput from "./hooks/useInput";

const Form = () => {
  const [name, connectName] = useInput("");
  const [age, connectSurname] = useInput("");
  return (
    <form>
      // destrukturyzacja zwracanego obiektu przez custom hooka
      //, który jest tablicą {value, onChange: e=>{setValue(e.target.value)}}
      <input type="text" {...connectName}/> 
      <input type="number" {...connectSurname}/>
    </form>
    );
};

Formularze wielokrotnego wyboru (select -> option)

W pewnym sensie swoją własną specyfiką charakteryzuje się formularze wielokrotnego wyboru

W przypadku formularza wielokrotnego wyboru ten domyślnie jako wybraną opcję przyjmie pierwszą z możliwych - stąd jeśli będziemy chcieli ją wybrać zmiana nie zostanie odnotowana. W przypadku poniższego formularze nie możemy wybrać wartości c3po i ta staje się dostępna dopiero wówczas gdy zostanie wybrana jako kolejna wartość - jak temu zapobiec?

See the Pen MWyvRVX by Mikołaj Kostyrko (@mkostyrko) on CodePen.

Rozwiązanie problemu

1 - Możemy w stanie przypisać wartość początkową, która później ulegnie zmianie i wskazuje użytkownikowi na wartość, która jest domyślną np ms

2 - To rozwiązanie nie musi być jednak zawsze satysfakcjonujące stąd można jako pierwszą opcję wyboru dodać pozycję nie posiadającą wartości a "wypełnioną" tekstem np.

<option value="">Select option</option>

Przykładowo

import ReactDOM from "react-dom";
import React, {useState} from "react";

const Form = () => {
  const [droid, setDroid] = useState(''); // alternatywnie [droid, setDroid] = useState('c3po');

  const handleDroidChange = (e) => {
    setDroid(e.target.value);
  };

  return (
    <form>
      <h1>Droid: {droid}</h1>

      <select value={droid} onChange={handleDroidChange}>
        // w alternatywnej wersji ta linia kodu nie jest potrzebna
        <option value="">Select option</option>
        <option value="c3po">C3PO</option>
        <option value="bb-8">BB-8</option>
        <option value="r2d2">R2D2</option>
      </select>
    </form>
  );
};

Radio input

W przypadku tzw inputu radiowego należy opcje wyboru zapakować w div lub inny element a następnie nasłuchiwać na zmiany jakie w nim zajdą / podobnie jak w przypadku inputu select

<div onChange={e=>handleChange(e)}>
    <input type="radio" value="male" name="gender"/> Male
    <input type="radio" value="female" name="gender"/> Female
    <input type="radio" value="not given" name="gender"/> I don't wanna say
</div>

:: W jaki sposób sprawić aby przy pomocy Radio input istniała możliwość zaznaczenia tylko jednej opcji? Najłatwiej to to osiągnąć poprzez wskazanie wspólnej nazwy => name dla wszystkich inputów a przez to i stowrzenie ich wspólnej grupy (jak w powyższym przykładzie)::

można również skorzystać z opcji stworzenia własnego hooka np. useRadioButtons

źródło przykładu StackOverflow-How to use radio buttons in ReactJS?

function useRadioButtons(name) {
  const [value, setState] = useState(null);

  const handleChange = e => {
    setState(e.target.value);
  };

  const inputProps = {
    name,
    type: "radio",
    onChange: handleChange
  };

  return [value, inputProps];
}rgba(255, 255, 255, 0)

użycie

function App() {
  const [platformValue, plaftormInputProps] = useRadioButtons("platform");
  const [genderValue, genderInputProps] = useRadioButtons("gender");
  return (
    <div>
      <form>
        <fieldset>
        <fieldset>
          Male
          <input
            value="male"
            checked={genderValue === "male"}
            {...genderInputProps}
          />
          Female
          <input
            value="female"
            checked={genderValue === "female"}
            {...genderInputProps}
          />
        </fieldset>
      </form>
    </div>
  );

Walidacja formularza

Walidacja może odbywać się na różne sposoby, ale jednym z prostszych a przez to ulubionych moich rozwiązań jest zastosowanie tablicy zbierającej informację o poszczególnych błędach, tą można przekazać do stanu a następnie wyrenderować

Przykładowo

const validate = () => {
  const errorsArr = []
  if (!name || name.length < 5 && name.length >60 && !isNan(name) ) 
  {errorsArr.push('Name must be at least 2 characters and max of 60 and not a number')}
  if (!planet || planet.length < 5 && planet.length >60 && !isNan(planet) ) 
  {errorsArr.push('Planet must be at least 2 characters and max of 60 and not a number')}
  if (!age || age < 20) // input musi być type='number'
  {errorsArr.push('Droid should be at least 20 and must be a number')}
  if (errorsArr.length!==0) {
    setError(()=>errorsArr)
    return false
  } else {
    setError(null)
    return true
  }
}

Taką funkcję walidującą można wykorzystać w innym miejscu danego komponentu np. w momencie przesyłania danych do serwera

  const [isSubmitted, setIsSubmitted] = useState(false);
  const [error, setError] = useState(null);

  const handleClick = (e) => {
    e.preventDefault(); // strona nie ma się odświeżać
    if(validate()) { // jeśli validate() true
      setIsSubmitted(true); // odpowiada za wyświetlania informacji o poprawnym zapisie
      fetch(API, {
        method: "POST",
        body: JSON.stringify(droid),
        headers: {
          "Content-Type": "application/json"
        }
        })
        .then(response => response.json())
        .then(droid => {
          // console.log(data);
          if(typeof addDroid==='function'){
            addUser(droid)
          }
        })
        .catch(error => {
          console.log(error);
          setError(error)
        });
        setTimeout(function(){ setIsSubmitted(false) }, 2000)
        // po upływie 2 sekund informacja znika 
    } else {
      setIsSubmitted(false);
    }
  }

  return (
    <>
      [...]
      // jeśli isSubmitted = true to wyświetl
      {isSubmitted && <h3>Data has been saved</h3>}

      // jeśli isSubmitted != null/false to wyświetl
      {error &&<ul>{error.map((error,i)=><li key={i}>{error}</li>)}</ul>}
    
  )

Więcej informacji na temat forms:

formularze - React docs

A Simple React.js Form Example

React form validation library built under 5kB

GitHub: react-hook-form

YT- The BEST Way To Create Forms In React - React Hook Form Tutorial - How To Create Forms In React

StackOverflow-How to use radio buttons in ReactJS?