javascript - Next.js: `onSubmit` kör inte återuppringningsfunktionen


I min Next.js-modul har jag ett formulär deklarerat på följande sätt:

<form onSubmit = {() => {
async() => await getCertificate(id)
.then(async resp => await resp.json())
.then(data => console.log(data))
.catch(err => console.error(err))
console.log("getCertificate completed")
}}>

Funktionen getCertificate() definieras i samma .tsx-fil som:

async function getCertificate(id: string) : Promise<Response> {
  console.log("in getCertificate")
  const resp = await fetch(`${serverUri}/certs/${id}`)
  console.log(`Response data: ${await resp.json()}`)
  return resp
}

Det verkar som att getCertificate() inte alls blir anropad. Medan den sista console.log("getCertificate completed") inne i min onSubmit-hanterare körs korrekt, körs ingen av console.log()-satserna inne i getCertificate().

Inga fel kastas eller loggas till konsolen. Det enda som visas i konsolen (förutom getCertificate completed) är Navigated to http://localhost:3000/?certId=my_cert_id.

Någon idé om varför det beter sig så här?

(De enda andra sakerna att notera är att min .tsx-fil är dekorerad med 'use client' eftersom jag använder ett klientbibliotek för vissa andra funktioner, och att min formulärdefinition finns inuti return-satsen av en export default function. Allt annat fungerar som förväntat förutom att getCertificates() inte blir träffad).

Svar #1

Möjlig lösning

Om det enda problemet är att få getCertificate att köras, kan du försöka göra något liknande detta:

import React, { useState } from "react";

const serverUri = "http://myserver.com";

export default function Certificate() {
  const [id, setId] = useState("");
  async function getCertificate(id: string): Promise<Response> {
    console.log("in getCertificate");
    const resp = await fetch(`${serverUri}/certs/${id}`);
    console.log(`Response data: ${await resp.json()}`);
    return resp;
  }
  return (
    <form onSubmit={async (event: any) => {
      event.preventDefault();
      try {
        const response = await getCertificate(id);
        const data = await response.json();
        console.log(data);
      } catch (err) {
        console.error(err);
      }
      console.log("getCertificate completed");
    }}>
      <label>
        Certificate ID:
        <input type="text" value={id} onChange={e => setId(e.target.value)} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

Som du kan se på skärmdumpen, skrivs in getCertificate ut till konsolen.

Anledningen till att det misslyckas med att utföra

Låt oss titta närmare på den ursprungliga funktionen:

() => {
    async() => await getCertificate(id)
    .then(async resp => await resp.json())
    .then(data => console.log(data))
    .catch(err => console.error(err))
    console.log("getCertificate completed")
}

Som du kan se här, definierar du bara en anonym asynkron pilfunktion här (utan att tilldela den funktionen till någon variabel, vilket innebär att denna funktion aldrig anropas) och sedan bara kör console.log. Så console.log är det enda som faktiskt körs.

Du lägger också till await före anropet till getCertificate, det är inte nödvändigt och kan leda till ytterligare förvirring.

Låt oss förenkla denna kodrad

async() => await getCertificate(id)
.then(async resp => await resp.json())
.then(data => console.log(data))
.catch(err => console.error(err))

Så först kommer vi att omskriva koden för att se strukturen:

async() => { 
    await getCertificate(id)
        .then(async resp => await resp.json())
        .then(data => console.log(data))
        .catch(err => console.error(err))
}

Sedan tar vi bort onödig funktionsdefinition:

await getCertificate(id)
    .then(async resp => await resp.json())
    .then(data => console.log(data))
    .catch(err => console.error(err))

Då tar vi bort onödigt await:

getCertificate(id)
    .then(async resp => await resp.json())
    .then(data => console.log(data))
    .catch(err => console.error(err))

En annan lösning

Så genom att använda transformationer i föregående avsnitt kan vi få ett annat sätt att definiera hela funktionen.

(event: any) => {
    event.preventDefault();
    getCertificate(id)
        .then(async resp => await resp.json())
        .then(data => console.log(data))
        .then(() => console.log("getCertificate completed with no errors"))
        .catch(err => console.error(err))
        .then(() => console.log("getCertificate completed with errors"))
}

P.S.

Observera att event.preventDefault() i båda lösningarna kommer att blockera den faktiska överföringen av formulärdata och kommer att tvinga hanteringen av allt i JS istället. Det är valfritt och kan utelämnas.

Svar #2

Du kapslar in ditt getCertificate-anrop i en anonym funktion som du faktiskt aldrig anropar. Av utseendet att döma behövs den wrapper-funktionen inte alls, så du kan helt enkelt flytta dess interna logik upp ett nivå till onSubmit-funktionen direkt, på det här sättet:

<form onSubmit = {async () => {
    try {
        const resp = await getCertificate(id);
        const data = await resp.json();
        console.log(data);
        console.log("getCertificate completed");
    } catch (err) {
        console.error(err)
    }
}}>

eller använda löfteskedjning:

<form onSubmit = {() => {
    getCertificate(id)
        .then(async resp => await resp.json())
        .then(data => console.log(data))
        .then(() => console.log("getCertificate completed"))
        .catch(err => console.error(err))
}}>

Och jag skulle rekommendera att du håller dig till antingen async/await eller kedjning av promises istället för att blanda dem, eftersom blandning gör logiken lite svårare att följa. Personligen rekommenderar jag async/await.