javascript - Trimiterea unui formular cu date folosind un hook React personalizat


Am probleme să înțeleg asta. Vreau să creez un hook care să fie apelat pentru a trimite un formular folosind fetch.

Acesta este ceea ce am acum. Componenta care deține formularul:

const MyForm = (): ReactElement => {
    const [status, data] = useSubmitForm('https://myurl-me/', someData);
    
    return <>
        <div className='Feedback-form'>
            <div className='body'>
                <form>
                    <input type='text' name='username' placeholder='name' required />
                    <input type='email' name='email' placeholder='email' required />
                    <button className='submit-feedback-button' type='button'>Send feedback</button>
                </form>
            </div>
        </div>
    </>
}

Hook-ul personalizat:

import { useState, useEffect } from 'react';

const useSubmitForm = (url: string, data: URLSearchParams): [string, []] => {

    const [status, setStatus] = useState<string>('idle');
    const [responseData, setData] = useState<[]>([]);

    useEffect(() => {
        if (!url) return;

        const fetchData = async () => {
            setStatus('fetching');

            const response = await fetch(url, {      
                method: 'POST',
                headers: {
                    'Accept': 'text/html',
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: data
            });

            const data = await response.json();

            setData(data);
            setStatus('fetched');
        };

        fetchData();
    }, [url]);

    return [status, responseData];
};

export default useSubmitForm;

Problema mea este că cred că acest hook este apelat imediat. Cum pot să fac acest hook și să-l apelez într-un mod în care să fie apelat doar atunci când formularul este trimis și toate datele de care am nevoie pentru a fi incluse în corpul cererii sunt acolo?

Răspuns #1

Ai dreptate, efectul rulează o singură dată când componenta se montează și deoarece url este adevărat, sare peste returnarea timpurie și invocă fetchData.

Cum fac acest hook și îl apelez într-un mod în care să fie apelat doar atunci când formularul este trimis și toate datele de care am nevoie pentru a fi trimise în corpul cererii sunt acolo pentru a fi incluse?

Trebuie să returnezi și o funcție pentru ca componenta să o apeleze și să transmită valorile câmpurilor formularului. Cred că ai câteva opțiuni de bază.

  1. Convertește câmpurile formularului pentru a fi inputuri controlate și stochează starea câmpurilor în componentă și apelează o funcție „fetch” returnată de hook-ul useSubmitForm.
  2. Returnează un handler onSubmit din useSubmitForm pentru a-l atașa la elementul tău form. Totuși, handler-ul onSubmit va trebui să știe ce câmpuri să acceseze din evenimentul onSubmit, deci trecerea unui array de nume de câmpuri către hook (adică un „config”) are sens.

Soluția 1 - Folosește intrări controlate și funcția fetch returnată

Despachetează funcția fetchData din hook-ul useEffect și adaugă-i un parametru de date pentru câmpul formularului. Deoarece fetch și response.json() pot arunca erori/rejectări, ar trebui să înconjori acest bloc cu un try/catch. Returnează funcția personalizată fetchData pentru ca formularul să o poată invoca.

foloseșteTrimiteFormular

const useSubmitForm = (
  url: string,
  data: URLSearchParams
): [function, string, []] => {
  const [status, setStatus] = useState<string>("idle");
  const [responseData, setData] = useState<[]>([]);

  const fetchData = async (formData) => {
    setStatus("fetching");

    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          Accept: "text/html",
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body: JSON.stringify(formData)
      });

      const data = await response.json();

      setData(data);
      setStatus("fetched");
    } catch (err) {
      setData(err);
      setStatus("failed");
    }
  };

  return [fetchData, status, responseData];
};

MyForm

const MyForm = (): ReactElement => {
  const [fields, setFields] = useState({ // <-- create field state
    email: '',
    username: '',
  });
  const [fetchData, status, data] = useSubmitForm(
    "https://myurl-me/",
    someData
  );

  useEffect(() => {
    // handle successful/failed fetch status and data/error
  }, [status, data]);

  const changeHandler = (e) => {
    const { name, value } = e.target;
    setFields((fields) => ({
      ...fields,
      [name]: value
    }));
  };

  const submitHandler = (e) => {
    e.preventDefault();
    fetchData(fields); // <-- invoke hook fetchData function
  };

  return (
    <div className="Feedback-form">
      <div className="body">
        <form onSubmit={submitHandler}> // <-- attach submit handler
          <input
            type="text"
            name="username"
            placeholder="name"
            onChange={changeHandler} // <-- attach change handler
            value={fields.username}  // <-- pass state
          />
          <input
            type="email"
            name="email"
            placeholder="email"
            onChange={changeHandler}  // <-- attach change handler
            value={fields.email}  // <-- attach state
          />
          <button className="submit-feedback-button" type="submit">
            Send feedback
          </button>
        </form>
      </div>
    </div>
  );
};

Soluția 2 - Returnează un handler onSubmit și transmite un array de câmpuri către useSubmitForm

foloseșteTrimiteFormular

const useSubmitForm = (
  url: string,
  data: URLSearchParams,
  fields: string[],
): [function, string, []] => {
  const [status, setStatus] = useState<string>("idle");
  const [responseData, setData] = useState<[]>([]);

  const fetchData = async (formData) => {
    setStatus("fetching");

    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          Accept: "text/html",
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body: JSON.stringify(formData)
      });

      const data = await response.json();

      setData(data);
      setStatus("fetched");
    } catch (err) {
      setData(err);
      setStatus("failed");
    }
  };

  const onSubmit = e => {
    e.preventDefault();

    const formData = fields.reduce((formData, field) => ({
      ...formData,
      [field]: e.target[field].value,
    }), {});
    fetchData(formData);
  }

  return [onSubmit, status, responseData];
};

MyForm

const MyForm = (): ReactElement => {
  const [onSubmit, status, data] = useSubmitForm(
    "https://myurl-me/",
    someData,
    ['email', 'username'] // <-- pass field array
  );

  useEffect(() => {
    // handle successful/failed fetch status and data/error
  }, [status, data]);

  return (
    <div className="Feedback-form">
      <div className="body">
        <form onSubmit={onSubmit}> // <-- attach submit handler
          <input
            type="text"
            name="username"
            placeholder="name"
          />
          <input
            type="email"
            name="email"
            placeholder="email"
          />
          <button className="submit-feedback-button" type="submit">
            Send feedback
          </button>
        </form>
      </div>
    </div>
  );
};

În opinia mea, a doua soluție este soluția mai curată și necesită mai puține componente consumatoare pentru a fi utilizată.