Галоўная / Артыкулы / Формулар даты нараджэння, пераканалены ў Next.js, з керованымі вводамі та функціями-вызовамі.

Формулар даты нараджэння, пераканалены ў Next.js, з керованымі вводамі та функціями-вызовамі.

Створыце маленькі кліентскі формулар у Next.js, який ведае даныя з вводу за дапамою useState, адхронізуе некоректныя даты нараджэння, паказвае зрозумелыя паведамленні пра бяглыню і передае чыстыя даныя своему родныму компоненту.

2384 слоў

Для стварэння прогназа аплікэцыя патрэбуе два данні ад візітара: імя та дату нараджэння. Гэта здаецца формай на пяць хвілін, але нават такi маленькi элемент вымагае серьёзных дыячэнняў па дизайну: дзе ён будзе функцыонаваць у аплікэцыі Next.js, хто будзе володаром уведзеных значэнняў, як будуць адхіляцыя некоректных дат, і хто будзе адлічваць, што будзе далей пасля успешнай адправкі.

У гэтым карце практычныя нарады пашагоўна ствараюць гэтую форму. До канца вы будете мяць компонент формы, які пераверчае уведзеныя данні, звестным чынам адмаўляе бяглыя падазры і праходзіць толькі чыстыя данні да вышэй, а таксама ясную ментальную модель, яю можна будзе перыскорыць для будзь-якай формы, якая складаецца з колькох полей.

Адлічыце, за шта несе адпаведальнасць компонент

Перш чым пісаць будзь-які JSX, карэткае перымясцаваць задачі компонента. У гэтай формы іх ровна трох:

  1. Зберагаць тое, што увёў корыстнік.
  2. Пераверыць уведзеныя данні пры адправцы.
  • Працэспактныя даны трэба вернуць у родную складовую.
  • Усё, што не ў гэтым списку, напрыклад, вызов API або адрасаванне рэзультата чытання, належыць куды іншае. Короткі список робіць рэшту дизайна простым.

    Чаму форма павінна быть кліентскай складовай

    У App Router кожная складовая є серверскай, якщо толькі вы не выберазе іншае. Серверскія складовыя адрасаваюцца на сервере і не выканаліваюць інтэрактыўны JavaScript, таму яны не можаць зберагаць стан або реагаваць на запускі. Таму форма пачынаецца з дырэктывы client.

    "use client";
    

    Складовая залежыць ад кількох элементаў, якія існуюць толькі ў браузеры:

    • useState для текущых значэнняў,
    • обрабочыкі onChange на элементах вводу,
    • обрабочык onSubmit на форме,
    • постаўленая інтэракцыя з корыстнікам,
  • Параболіка, яка выконвачыцца прытаму, перш чым ўсё будзе адправлена.
  • Корача кажучы, такі компонент не проста адображае інфармацыю; ён должен рэагаваць на дзеяння корыстувальца. Гэта і є сігналам для пазначэння яго як Кліентскага компонента. Корыстным прыемам є трэбаць, каб такія компоненты былі маленькімі і знаходзіліся на канцох дрэва структуры, тады дыректыва не будзе включаць большыя часткі сторанкі ў кліентскі пакет.

    Увводзіце даны і пропсы

    Форма імпортуе спяльны тип Profile разам з useState.

    import { Profile } from "../types";
    import { useState } from "react";
    

    Явна дэкларацыя формату адправляемых даных значыць, што TypeScript можа пераканалізаваць кожны месца, дзе яны ствараюцца або вжываюцца, замест таго, каб хто-небудзь могаў перадаваць довільны об’ект праз прыкладку.

    export type Profile ={
     name: string;
     dob: string;
    }
    

    Далей ідуць пропсы компонента. Їх толькі адны: калебэк, які падае з верхньага компонента.

    type HoroscopeFormProps = {
      onSubmit: (info: Profile) => void;
    };
    

    Дазвольце верхньаму компоненту выбраць, што будзе далей

    Этот элемент ёсць там, дзе практыкуецца межа компаненту. Форма збірае даны, але яна не мае права сама вяршыць, што з німі робіць. Залежна ад экрана, родны компанент можа:

    • звярнуцца да API,
    • сгенераваць гораскоп,
    • зберагчы профіль,
    • паказаць рэзултат,
    • адкоректаваць іншы стан.

    Якщо будзе застосаваны жорсткі код якога-небудзь з гэтых варыянтов у самай форме, яна стане прычаўаная да аднаго экрана. Узьмець функцыю onSubmit заместа гэтаго дазволяе ўсё ж выкарыстоўваць форму знову. Тып паказывае, што onSubmit прымеў Profile і нічога не вяртае (void), таму форма ўвядзе яе і праходзіць да наступнага крока. Загальны прайсэп выглядае так:

    User enters information
            ↓
    HoroscopeForm collects it
            ↓
    HoroscopeForm validates it
            ↓
    onSubmit(user)
            ↓
    Parent decides what happens next
    

    Кожны крок мае адного власніка, і частка формы завершаецца ў той момент, калі яна вызывае калбэк.

    Зберагчы вхідныя даны ў стане React

    Форма патрабуе месца для зберагчыць текущыя значэнні. Тры элементы стану пакрываюць усё.

    const [name, setName] = useState<string>("");
    const [dob, setDob] = useState<string>("");
    const [error, setError] = useState<string>("");
    

    Спачатку паглядзіце на назву.

    const [name, setName] = useState<string>("");
    

    useState вяртае пару. name — это текущае значэння, а setName — функцыя, якую вы вызываеце, каб заменіць йое, яка таксама запланавае парадрэнд. Пачатковым значэнням ёст моўны шаблон, таму што нічога ўсё яшчэ не было введзена. Ініцыялізацыя стрэлкай заместо undefined мае значэнне для кантроліруемых вводоў: React паведамляе пра змяну, калі ввод пераходзіць з некантроліруемага у кантроліруемы, калі його value зменяецца з undefined на стрэлку.

    Дата нараджэння таксама прыменяе той жа принцып.

    const [dob, setDob] = useState<string>("");
    

    Паслядні элемент храніць текущаяе паведамленне пра адзінак.

    const [error, setError] = useState<string>("");
    

    Пустая строчка означае, што наразе няма якога-лібо бяга. Пры перапрацоўкі даных будзе запісана паведамленне, калі ўтварыцца проблема, а яна будзе вычышана, калі даннэ прыймуцца.

    Зберагаеце перапрацоўку дат у самастойной функцыі

    Правіла перапрацоўкі даных часта расшырваюцца, таму замест таго, каб сумаць іх у функцыі для адправкі, перапрацоўка дат выканаецца ў спецыяльной функцыі. Їё сігнатура паведамляе пра тое, якія даннэ трэба адправіць.

    function validateDOB(dob: string): string | null {
    

    Є роўна два рэзультата. Невярная дата прадае строчку, якая пояснюе проблему; правільная дата прадае null.

    Valid date
       ↓
    return null
    
    Invalid date
       ↓
    return error message
    

    Пакалькі функцыя адпавядае на аднае запытанне — „Чы гэта дата нараджэння ўзьяць?“, таму яе лёгка чытаць, лёгка працаваць з яю ў тэстах без адрасавання чаго-небудзь, і лёгка перадаць на сервер, якщо пазней будзе адбывацца верыфікацыя там таксама. Верненне паведамлення замест абракцэсу робіць код, які яе вызывае, простым: пераканацца ў рэзультате, праказаць яго, якщо ён існуе.

    Калія даты трэба адхіліць

    Для даты нараджэння паслужаюць два правіла:

    • Няма дат у будучыні. Не можа быць, каб хтось нарадзіўся ў дзень, які ўсё ще не настаў.
    • Разумны ніжнія межы. Даты, якія знаходзяцца болей чым за 150 гадоў у мінулом, адхіляюцца, каліколькі ў большасці случаў яны ўводзяцца некоректна.

    Даты здаюця простымі, калі не врачацца час дзёны. Элемент <input type="date"> вяртае строку у формате YYYY-MM-DD, а выраз new Date("2024-05-01") адначы спрыяе тлумачэнню гэтай строкі як паўначародзі UTC, тады калі ж выраз "today", створаны з new Date(), включае местны час, хвілінкі і секунды. Залежна ад часовага поясу пользователя, простая параболка може неправильна прыйняць завтрашні дзень або адхіліць сённяшні. Два надзеяныя спосабы — або нормалізаваць обе стороны да пачатку дзёны пры параболце, або спачатку пораўняваць строкі у формате YYYY-MM-DD, якія правільна сортуюцца як тэкст. Незалежна ад выбранага падходу, і незалежна ад таго, чыя помагала вам штучная інтэлектуальная асистэнцыя, будзьте впевнены, што можаце поясніць, чаму прадбачаны кожны такі пораўнак; багі з датамі часта хаваюцца самэль у тых рядках, якія ніхто не зрозумеў.

    Усё координаваць у функцыі обробкі выкарыстоўвання данных

    Калі дадзеныя і пераканальніцтва гатовы, handleSubmit спаўнае іх між сабою. Калі корыстнік надае данні, функцыя должна:

    1. зупініць стандартную дзеянне прыёмніка, якае б перазавантажыла або перейшла на іншую стороніцу,
    2. пераканацца, што ў обох польхаў є значэнні,
    3. пераканацца ў правямоўнасці даты нараджэння,
    4. паказаць памылку, якшо ў чымсь є проблема,
    5. інакш чым перадаць данні родным элементу.

    Усё пачынаецца так.

    const handleSubmit = (e: React.SubmitEvent) => {
      e.preventDefault();
    

    Па значэнню стандарту, надасць данні формы значыць адправіць запит і перазавантажыць стороніцу. Адказна React керуе процэсам надасця данні, таму гэты стандарт трэба анулюваць.

    e.preventDefault();
    

    З гэтага момента сам компонент вырашае, што будзе робіць падача даных. Прыметка пра тип з’явы: у багатьох кодаваях гэты параметр пазначаецца як React.FormEvent<HTMLFormElement>. Пераканаўцеся, якія типы з’явы падачы даных падтрымлівае ваша встановленая версія @types/react, і выберыце той, які ўсё час викорыстоўваецца у вашам проекте.

    Адхіліць порожнія полья за дапамою раннега варыць

    Перш чым пераканаўцеся, чы роць/месца/дзень маюць сэнс, спачатку пераканаўцеся, чы ўзагалі было введзена якое-небудзь даныя.

    if (!name || !dob) {
      setError("Please enter in information");
      return;
    }
    

    Якщо або ўсё жае поле пустое, працоўнік фіксуе адказку і негайна вяртаецца. Цэй — шаблон ранней вяртання (альбо клазулі захавання): калі вядома, што даныя невалідныя, нема чаго робіць, таму функцыя завершаецца замест таго, каб захаваць рэшту логіки ў іншым роўні блакоў if. Кожная клазула захавання караціцца адной неудачай, а „выпаважаны шлях“ застаецца простым.

    Адрыхтуйчы перакананне па дате

    Калі вядома, што дата існуе, яна праходзіць через верыфікатор.

    const dobError = validateDOB(dob);
    

    Рэзультат — гэта або паведамленне, або null, таму дапамагае адна перакананне.

    if (dobError) {
      setError(dobError);
      return;
    }
    

    Паведамленне значыць, што працоўнік яго паказвае і зупыняецца. null значыць, што дата паспела, і выконанне продовжваецца.

    Перадайчы чыстыя даныя роднім элементам

    Дасягненне гэтага пункту значыць, што всі перакрыцця пройшлі, таму будзь-якія застарэлыя паказанні пра адзінкі з ранейшых спроб былі усунуты.

    setError("");
    

    Пасля чаго функцыя-вызов абяцейкі-родніка прыме паверыраны профіль.

    onSubmit({ name, dob });
    

    Гэта ўсуненне наследкав перадвыбранага ранейша рашэння па дизайне. Форма не ведае і не цікавіцца тым, што будзе далей; яна проста паведамляе, што є валідныя даны, а абяцейка-роднік выбирае, што робіць. Той самы компонент могае сьведчыць генератору гораскопа сёння і экрану налашоўкаў профіля завтра без якіх-леба змян.

    З’ўязаць логіку з маркапам

    Элемент формы пав’язуе адправку даных з працоўнікам, який іх обрабоцуе.

    <form onSubmit={handleSubmit}>
    

    Гэта прыказвае React запускать handleSubmit кожны раз, калі форма адправляецца, незалежна ад таго, чы то праз натыканне на кнапку чы прасунуць клавішу Enter у поле. Далей йдзе поле з іменам.

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

    Як контроліруемы вхід застаецца сінхронным

    Это ўражынка контролюемага вводу: джэрамент правды для яе значэння — стан React, а не DOM. Кожны раз, калі корыстнік пішае, запускаецца функцыя обробкі змены.

    onChange={(e) => setName(e.target.value)}
    

    Яна чытае новы текст з запускаючага заходу і зберагае яго ў стане. Полны цикл выглядае так:

    User types
        ↓
    onChange fires
        ↓
    setName(new value)
        ↓
    name state updates
        ↓
    value={name}
        ↓
    Input displays updated value
    

    Пакалькі ўражынка завжды показвае тое, што зберагаецца ў name, значэнне, якое вы пераканалі, неабходна будзе таксама быць тым, што апісваецца на экране. Поле з датамі выкарыстоўвае той самы прынцып.

    <input
      type="date"
      value={dob}
      onChange={(e) => setDob(e.target.value)}
    />
    

    Стан фіксуе выбраную дату, а кожная змена вызывае setDob. Яшчэ адна дыяктыва, яку варта рассмотрзець — это наявнасць атрыбута max, заданага на сучасную дату, які не дазволяе большасці праграм для выбору дат показваць дні, якія ўжо прыйшлі, тады как ваша функцыя пераканалення яшчэ захоўвае захіст ад некоректнага вводу і старэйшых прыгледачоў.

    Кожны ўвод таксама павінен маты видимы <label>, які ўзаеўна з’яўляецца з ям. Заместнік або суседня загаловка не ўзьмуцца за замену; самэ лейбл яе чытаюць прыстроі для адзычнага чытання, і самэ за дапамогою яго пазначэння поля стае можна клікнуць.

    Апыляйце паведамленні пра адзінкі толькі калі яны існуюць

    Паведамленне пра адзінку павінна з’яўляцца толькі тады, калі яна є. Для гэтага выкарыстоўваецца умовная візуалізацыя.

    {error && (
      <p role="alert">
        {error}
      </p>
    )}
    

    Калі змэнная error мае текст, параграф візуалізуецца; калі ёй являецца порожняя строка, якая супадае з значэннем false, нічога не апыляецца. Шорткат && тут ў безпецы, калі толькі значэнне є стракой. З цыфрамі гэта можа спрацаваць некоректна: лік 0 будзе візуалізавацца як буквальна цифра „0“.

    Параграф таксама мае ролю ARIA.

    role="alert"
    

    role="alert" паведамляе асаблівайным тэхналогіям, што гэты кантэнт ўажлівы і часава значны, таму скрін-чытальнікі аанунсуюць яго як толькі ён з’являецца. Цэе змена адзінаго атрыбута, якая дазволяе викорыстоўваць адзывы пра падтверджэння для людзей, якія не можаць бачыць апавесцеленне. Для большай яснасі вы таксама можете пазначыць проблемны поле атрыбутам aria-invalid і запяроўваць яго з адзывам за дапамогою aria-describedby.

    Дадзіце кнопку «Адправіць»

    Пасляльнім элементам ёсць кнопка, якая чыстаючы выказана як кнопка адправіць.

    <button type="submit">
      Submit
    </button>
    

    Унутры формы кантакт з type="submit" запускае метод onSubmit формы, а разам і handleSubmit. Кантакты ўнутры формы за замовчанням таксама выконваюць адправку, але указванне типу запобегае неспадзянкам, калі хтось пазней дада другі кантакт, прызначанный для іншай меты, напрыклад, для чыставання поль.

    Полны тэкучы ляўка дадзеных

    Якща аднацца ў цэласці, компанента перадае дадзеныя ў аднам напрамку:

    State
      ↓
    User input
      ↓
    Submit
      ↓
    Validation
      ↓
    Parent callback
    
    • useState зберагае тое, што увёў корыстнік.
    • Полья апошньвуюць гэты статус пасля кожных змян.
    • Адправка формы запускае handleSubmit.
    • handleSubmit пераканальвае значэнні.
    • Некоректныя даны ствараюць статус адзінакоў і зупыняюць процес.
    • Коректныя даны пераходзяць да роднічага компонента через onSubmit, і роднічы компонент ўзяўшы іх з таму.

    Куда працаваць далей

    Гэты спосаб ручнага стварэння форм ўзмоўклівы для навучання і абліковы для форм з двума полямі. Калі колькасць полей у форме зрастае, а таксама патрабуюцца правіла між полямі аб пераказы з сервера, розгляньце варыянт з бібліятэкай схем, каб тыя ж правіла дзейнулі як на кліенте, так і на сервере; адна з магчымас — падзелка схемы Zod між фронтэндам на React і бэкендам на Node. Перакананне данных на кліенте павышае якасць викорыстоўвання, але ніколі не заменяе перакананне на сервере, адколькі будзь-які запит можна створыць вручную.

    Галоўныя выводы

    • Атрыбут "use client" трэба ставіць толькі інтэрактыўным компонентам і робіць іх маленькімі.
    • Нехай форма выпалняе адну задачу: збірае даны, перакананае іх і передае далей. Нехай родны компонент керуе пасылкамі дадзеных через типаваны калебэк.
  • Ўзайце кантролюемыя вводныя элементы, ініцыялізаваныя стрэлкамі, так каб значэнне на экране было тым самым, якое вы перакантролюе.
  • Практыкуйце правіла перакантролювання ў чыстых функціях, якія вяртаюць паведамленне або null; іх лёгка тэставаць і перызнаць.
  • Аккуратна ставіцеся да дат: нормалізуйце час дня або парабяльвайце стрэлкі YYYY-MM-DD, каб ухіліцца ад памылак, вызваных часовымя зонамі.
  • Ўзайце ранніяе вяртанне, каб функцыя адправкі залишалася простай, і role="alert" плюс правільныя абзначэння, каб памылкі былі доступныя.
  • Магчымасць адказаць на пытанні, чаму існуе кожны рядок, а ўжо тым болей тыя, якія запропанаваў AI-асистэнт, ёсць часткай завершэння работы.