React 16x - قسمت 19 - کار با فرم‌ها - بخش 2 - اعتبارسنجی ورودی‌های کاربران
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهارده دقیقه

تمام فرم‌های تعریف شده، نیاز به اعتبارسنجی اطلاعات وارد شده‌ی توسط کاربران خود را دارند. ابتدا اعتبارسنجی اطلاعات را در حین ارسال فرم و سپس آن‌را همزمان با ورود اطلاعات، بررسی می‌کنیم.


اصول کلی طراحی یک اعتبارسنج ساده

در قسمت قبل، تمام اطلاعات فرم لاگین را درون شیء account خاصیت state قرار دادیم. در اینجا نیز شبیه به چنین شیءای را برای ذخیره سازی خطاهای اعتبارسنجی فیلدهای فرم، تعریف می‌کنیم:
class LoginForm extends Component {
  state = {
    account: { username: "", password: "" },
    errors: {
      username: "Username is required"
    }
  };
خاصیت errors تعریف شده، یک شیء را باز می‌گرداند که حاوی اطلاعات و خطاهای مرتبط با اعتبارسنجی فیلدهای مشکل دار است. بنابراین نام خواص این شیء، با نام فیلدهای فرم تطابق دارند. کار کردن با یک شیء هم جهت یافتن خطاهای یک فیلد مشخص، ساده‌تر است از کار کردن با یک آرایه؛ از این جهت که نیازی به جستجوی خاصی در این شیء نبوده و با استفاده از روش دسترسی پویای به خواص یک شیء جاوا اسکریپتی مانند errors["username"]، می‌توان خطاهای مرتبط با هر فیلد را به سادگی نمایش داد.
البته در ابتدای کار، خاصیت errors را با یک شیء خالی ({}) مقدار دهی می‌کنیم و سپس در متد مدیریت ارسال فرم به سرور:
  validate = () => {
    return { username: "Username is required." };
  };

  handleSubmit = e => {
    e.preventDefault();

    const errors = this.validate();
    console.log("Validation errors", errors);
    this.setState({ errors });
    if (errors) {
      return;
    }

    // call the server
    console.log("Submitted!");
  };
- ابتدا خروجی متد validate سفارشی را بررسی می‌کنیم که خروجی آن، خطاهای ممکن است.
- اگر خطایی وجود داشت، به مرحله‌ی بعد که ارسال فرم به سمت سرور می‌باشد، نخواهیم رسید و کار را با یک return، خاتمه می‌دهیم.
- علت فراخوانی متد setState در اینجا، درخواست رندر مجدد فرم، با توجه به خطاهای اعتبارسنجی ممکنی است که به خاصیت errors، اضافه یا به روز رسانی کرده‌ایم.
- نمونه‌ای از خروجی متد validate را نیز در اینجا مشاهده می‌کنید که تشکیل شده‌است از یک شیء، که هر خاصیت آن، به نام یک فیلد موجود در فرم، اشاره می‌کند.


پیاده سازی یک اعتبارسنج ساده

در اینجا یک نمونه پیاده سازی ساده و ابتدایی منطق اعتبارسنجی فیلدهای فرم را ملاحظه می‌کنید:
  validate = () => {
    const { account } = this.state;

    const errors = {};
    if (account.username.trim() === "") {
      errors.username = "Username is required.";
    }

    if (account.password.trim() === "") {
      errors.password = "Password is required.";
    }

    return Object.keys(errors).length === 0 ? null : errors;
  };
- ابتدا توسط Object Destructuring، خاصیت account شیء منتسب به خاصیت state کامپوننت را دریافت می‌کنیم، تا مدام نیاز به نوشتن this.state.account نباشد.
- سپس یک شیء خالی error را تعریف کرده‌ایم.
- در ادامه با توجه به اینکه مقادیر المان‌های فرم در state وجود دارند، خالی بودن آن‌ها را بررسی می‌کنیم. اگر خالی بودند، یک خاصیت جدید را با همان نام المان مورد بررسی، به شیء errors اضافه کرده و پیام خطایی را درج می‌کنیم.
- در نهایت این شیء errors و یا نال را (در صورت عدم وجود خطایی) بازگشت می‌دهیم.

برای آزمایش آن، پس از اجرای برنامه، یکبار بدون وارد کردن اطلاعاتی، بر روی دکمه‌ی Login کلیک کنید؛ یکبار هم با وارد کردن اطلاعاتی در فیلدهای مختلف. در این بین کنسول توسعه دهندگان مرورگر را نیز جهت مشاهده‌ی شیء‌های error لاگ شده، بررسی نمائید.



نمایش خطاهای اعتبارسنجی فیلدهای فرم

در قسمت قبل، کامپوننت جدید src\components\common\input.jsx را جهت کاهش کدهای تکراری تعاریف المان‌های ورودی، ایجاد کردیم. در اینجا نیز می‌توان کار نمایش خطاهای اعتبارسنجی را قرار داد:
import React from "react";

const Input = ({ name, label, value, error, onChange }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        value={value}
        onChange={onChange}
        id={name}
        name={name}
        type="text"
        className="form-control"
      />
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Input;
- در اینجا ابتدا خاصیت error را به لیست خواص مورد انتظار از شیء props، اضافه کرده‌ایم.
- سپس با توجه به نکته‌ی «رندر شرطی عناصر در کامپوننت‌های React» در قسمت 5، اگر error مقداری داشته باشد و به true تفسیر شود، آنگاه به صورت خودکار، div ای که دارای کلاس‌های بوت استرپی اخطار است به همراه متن خطا، رندر خواهد شد؛ در غیراینصورت هیچ div ای به صفحه اضافه نمی‌شود.
- اکنون متد رندر کامپوننت فرم لاگین را به صورت زیر تکمیل می‌کنیم:
  render() {
    const { account, errors } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        <Input
          name="username"
          label="Username"
          value={account.username}
          onChange={this.handleChange}
          error={errors.username}
        />
        <Input
          name="password"
          label="Password"
          value={account.password}
          onChange={this.handleChange}
          error={errors.password}
        />
        <button className="btn btn-primary">Login</button>
      </form>
    );
  }
در ابتدای متد رندر، با استفاده از Object Destructuring، خاصیت errors شیء منتسب به خاصیت state کامپوننت را دریافت کرده‌ایم. سپس با استفاده از آن، ویژگی جدید error را که به تعریف کامپوننت Input اضافه کردیم، در دو فیلد username و password، مقدار دهی می‌کنیم.

تا اینجا اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، با کلیک بر روی دکمه‌ی Login، خطاهای اعتبارسنجی به صورت زیر ظاهر می‌شوند:


در این حالت اگر هر دو فیلد را تکمیل کرده و بر روی دکمه‌ی لاگین کلیک کنیم، به خطای زیر در کنسول توسعه دهندگان مرورگر می‌رسیم:
loginForm.jsx:55 Uncaught TypeError: Cannot read property 'username' of null
at LoginForm.render (loginForm.jsx:55)
علت اینجاست که چون فرم اعتبارسنجی شده و مشکلی وجود نداشته‌است، خروجی متد validate در این حالت، null است. بنابراین دیگر نمی‌توان به خاصیت برای مثال username آن دسترسی یافت. برای رفع این مشکل در متد handleSubmit، جائیکه errors را در خاصیت state به روز رسانی می‌کنیم، اگر errors نال باشد، بجای آن یک شیء خالی را بازگشت می‌دهیم:
this.setState({ errors: errors || {} });
این قطعه کد، به این معنا است که اگر errors مقدار دهی شده بود، از آن استفاده کن، در غیراینصورت {} (یک شیء خالی جاوا اسکریپتی) را بازگشت بده.


اعتبارسنجی فیلدهای یک فرم در حین ورود اطلاعات در آن‌ها

تا اینجا نحوه‌ی اعتبارسنجی فیلدهای ورودی را در حین submit بررسی کردیم. شبیه به همین روش را به حالت onChange و متد handleChange فرم لاگین که در قسمت قبل تکمیل کردیم نیز می‌توان اعمال کرد:
  handleChange = ({ currentTarget: input }) => {
    const errors = { ...this.state.errors }; //cloning an object
    const errorMessage = this.validateProperty(input);
    if (errorMessage) {
      errors[input.name] = errorMessage;
    } else {
      delete errors[input.name];
    }

    const account = { ...this.state.account }; //cloning an object
    account[input.name] = input.value;

    this.setState({ account, errors });
  };
- ابتدا شیء errors را clone می‌کنیم؛ چون می‌خواهیم خواصی را به آن کم و زیاد کرده و سپس بر اساس آن مجددا state را به روز رسانی کنیم.
- سپس اینبار فقط نیاز داریم اعتبار اطلاعات ورودی یک فیلد را بررسی کنیم و متد validate فعلی، فیلدهای کل فرم را با هم تعیین اعتبار می‌کند. به همین جهت متد جدید validateProperty را به صورت زیر تعریف می‌کنیم. اگر این متد خروجی داشت، خاصیت متناظر با آن‌را در شیء errors به روز رسانی می‌کنیم؛ در غیراینصورت این خاصیت را از شیء errors حذف می‌کنیم تا پیام اشتباهی را نمایش ندهد. در نهایت توسط متد setState، مقدار خاصیت errors را با شیء errors جاری به روز رسانی می‌کنیم:
  validateProperty = ({ name, value }) => {
    if (name === "username") {
      if (value.trim() === "") {
        return "Username is required.";
      }
      // ...
    }

    if (name === "password") {
      if (value.trim() === "") {
        return "Password is required.";
      }
      // ...
    }
  };
در متد validateProperty، خواص name و value از شیء input ارسالی به آن استخراج شده‌اند و سپس بر اساس آن‌ها کار اعتبارسنجی صورت می‌گیرد.

پس از ذخیره سازی این تغییرات، برای آزمایش آن، یکبار حرف a را بجای username وارد کنید و سپس آن‌را حذف کنید. بلافاصله پیام خطای مرتبطی نمایش داده خواهد شد و اگر مجددا عبارتی را وارد کنیم، این پیام محو می‌شود که معادل قسمت delete در کدهای فوق است.


معرفی Joi


تا اینجا، هدف نمایش ساختار یک اعتبارسنج ساده بود. این روش مقیاس پذیر نیست و در ادامه آن‌را با یک کتابخانه‌ی اعتبارسنجی بسیار پیشرفته به نام Joi، جایگزین خواهیم کرد که نمونه مثال‌های آن‌را در اینجا می‌توانید مشاهده کنید:
const Joi = require('@hapi/joi');
const schema = Joi.object({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/),
    email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
});
schema.validate({ username: 'abc', birth_year: 1994 });
ایده‌ی اصلی Joi، تعریف یک اسکیما برای object جاوا اسکریپتی خود است. در این اسکیما، تمام خواص شیء مدنظر ذکر شده و سپس توسط fluent api آن، نیازمندی‌های اعتبارسنجی هرکدام ذکر می‌شوند. برای مثال username باید رشته‌ای بوده، تنها از حروف و اعداد تشکیل شود. حداقل طول آن، 3 و حداکثر طول آن، 30 باشد و همچنین ورود آن نیز اجباری است. با استفاده از pattern آن می‌توان عبارات باقاعده را ذکر کرد و یا با متدهایی مانند email، از قالب خاص مقدار یک خاصیت، اطمینان حاصل کرد.

برای نصب آن، پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
> npm install @hapi/joi --save
> npm i --save-dev @types/hapi__joi
که در نهایت سبب نصب کتابخانه‌ی node_modules\@hapi\joi\dist\joi-browser.min.js خواهند شد و همچنین TypeScript definitions آن‌را نیز نصب می‌کنند که بلافاصله سبب فعالسازی intellisense مخصوص آن در VSCode خواهد شد. بدون نصب types آن، پس از تایپ Joi.، از مزایای تکمیل خودکار fluent api آن توسط VSCode، برخوردار نخواهیم بود.

سپس به کامپوننت فرم لاگین مراجعه کرده و در ابتدای آن، Joi را import می‌کنیم:
import Joi from "@hapi/joi";
پس از آن، اسکیمای شیء account را تعریف خواهیم کرد. اسکیما نیازی نیست جزئی از state باشد؛ چون قرار نیست تغییر کند. به همین جهت آن‌را به صورت یک خاصیت جدید در سطح کلاس کامپوننت تعریف می‌کنیم:
  schema = {
    username: Joi.string()
      .required()
      .label("Username"),
    password: Joi.string()
      .required()
      .label("Password")
  };
خاصیت اسکیما را با یک شیء با ساختار از نوع Joi.object، که خواص آن، با خواص شیء account مرتبط با فیلدهای فرم لاگین، تطابق دارد، تکمیل می‌کنیم. مقدار هر خاصیت نیز با Joi. شروع شده و سپس نوع و محدودیت‌های مدنظر اعتبارسنجی را می‌توان تعریف کرد که در اینجا هر دو مورد باید رشته‌ای بوده و به صورت اجباری وارد شوند. توسط متد label، برچسب نام خاصیت درج شده‌ی در پیام خطای نهایی را می‌توان تنظیم کرد. اگر از این متد استفاده نشود، از همان نام خاصیت ذکر شده استفاده می‌کند.
سپس ابتدای متد validate قبلی را به صورت زیر بازنویسی می‌کنیم:
  validate = () => {
    const { account } = this.state;
    const validationResult = Joi.object(this.schema).validate(account, {
      abortEarly: false
    });
    console.log("validationResult", validationResult);
ابتدا مقدار خاصیت account، از شیء state استخراج شده‌است که حاوی شیءای با اطلاعات نام کاربری و کلمه‌ی عبور است. سپس این شیء را به متد validate خاصیت اسکیمایی که تعریف کردیم، ارسال می‌کنیم. Joi، اعتبارسنجی را به محض یافتن خطایی، متوقف می‌کند. به همین جهت تنظیم abortEarly آن به false صورت گرفته‌است تا تمام خطاهای اعتبارسنجی را نمایش دهد.
اکنون اگر برنامه را اجرا کرده و بدون ورود اطلاعاتی، بر روی دکمه‌ی لاگین کلیک کنیم، خروجی زیر در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:


همانطور که مشاهده می‌کنید، خروجی Joi، یک شیء است که اگر دارای خاصیت error بود، یعنی خطای اعتبارسنجی رخ‌داده‌است. سپس باید خاصیت آرایه‌ای details این شیء error را جهت یافتن خواص مشکل دار بررسی کرد. هر خاصیت در اینجا با path مشخص می‌شود. بنابراین قدم بعدی، تبدیل این ساختار، به ساختار شیء errors موجود در state کامپوننت جاری است تا مابقی برنامه بتواند بدون تغییری از آن استفاده کند.


نگاشت شیء دریافتی از Joi، به شیء errors موجود در state کامپوننت لاگین

خاصیت error شیء دریافتی از متد validate کتابخانه‌ی Joi، تنها زمانی ظاهر می‌شود که خطایی وجود داشته باشد. همچنین خاصیت details آن نیز آرایه‌ا‌ی از اشیاء با خواص message و path است. این path نیز یک آرایه است که اولین المان آن، نام خاصیت در حال بررسی است. اکنون می‌خواهیم این آرایه را تبدیل به یک شیء قابل درک برای برنامه کنیم:
  validate = () => {
    const { account } = this.state;
    const validationResult = Joi.object(this.schema).validate(account, {
      abortEarly: false
    });
    console.log("validationResult", validationResult);

    if (!validationResult.error) {
      return null;
    }

    const errors = {};
    for (let item of validationResult.error.details) {
      errors[item.path[0]] = item.message;
    }
    return errors;
  };
ابتدا بررسی می‌کنیم که آیا خاصیت error، تنظیم شده‌است یا خیر؟ اگر خیر، کار این متد به پایان می‌رسد. سپس حلقه‌ای را بر روی آرایه‌ی details، تشکیل می‌دهیم تا شیء errors مدنظر ما را با خاصیت دریافتی از path و پیام دریافتی متناظری تکمیل کند. در آخر این شیء errors با ساختار مدنظر خود را بازگشت می‌دهیم.

اکنون اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، همانند قبل می‌توان خطاهای اعتبارسنجی در حین submit را مشاهده کرد:



بازنویسی متد validateProperty توسط Joi

تا اینجا متد validate ساده و ابتدایی خود را با استفاده از امکانات کتابخانه‌ی Joi، بازنویسی کردیم. اکنون نوبت بازنویسی متد اعتبارسنجی در حین تایپ اطلاعات است:
  validateProperty = ({ name, value }) => {
    const userInputObject = { [name]: value };
    const propertySchema = Joi.object({ [name]: this.schema[name] });
    const { error } = propertySchema.validate(userInputObject, {
      abortEarly: true
    });
    return error ? error.details[0].message : null;
  };
تفاوت این متد با متد validate، در اعتبارسنجی تنها یک خاصیت از شیء account موجود در state است؛ به همین جهت نمی‌توان کل this.state.account را به متد validate کتابخانه‌ی Joi ارسال کرد. بنابراین نیاز است بر اساس name و value رسیده‌ی از کاربر، یک شیء جدید را به صورت پویا تولید کرد. در اینجا روش تعریف { [name]: value } به computed properties معرفی شده‌ی در ES6 اشاره می‌کند. اگر در حین تعریف یک شیء، برای مثال بنویسیم {"username:"value}، این username به صورت "username" ثابتی تفسیر می‌شود و پویا نیست. اما در ES6 می‌توان با استفاده از [] ها، تعریف نام یک خاصیت را پویا کرد که نمونه‌ای از آن‌را در userInputObject و همچنین propertySchema مشاهده می‌کنید.
تا اینجا بجای this.state.account که به کل فرم اشاره می‌کند، شیء اختصاصی‌تر userInputObject را ایجاد کرده‌ایم که معادل اطلاعات فیلد ورودی کاربر است. یک چنین نکته‌ای را در مورد schema نیز باید رعایت کرد. در اینجا نمی‌توان اعتبارسنجی را با this.schema شروع کرد؛ چون این شیء نیز به اطلاعات کل فرم اشاره می‌کند و نه تک فیلدی که هم اکنون در حال کار با آن هستیم. بنابراین نیاز است یک Joi.object جدید را بر اساس name رسیده، از this.schema کلی، استخراج و تولید کرد؛ مانند propertySchema که مشاهده می‌کنید.
اکنون می‌توان متد validate این شیء اسکیمای جدید را فراخوانی کرد و خاصیت error شیء حاصل از آن‌را توسط Object Destructuring، استخراج نمود. در اینجا abortEarly به true تنظیم شده‌است (البته حالت پیش‌فرض آن است و نیازی به ذکر صریح آن نیست). علت اینجا است که نمایش خطاهای بیش از اندازه به کاربر، برای او گیج کننده خواهند بود؛ به همین جهت هربار، اولین خطای حاصل را به او نمایش می‌دهیم.


غیرفعالسازی دکمه‌ی submit در صورت شکست اعتبارسنجی‌های فرم

در ادامه می‌خواهیم دکمه‌ی submit فرم لاگین، تا زمانیکه اعتبارسنجی آن با موفقیت به پایان برسد، غیرفعال باشد:
 <button disabled={this.validate()} className="btn btn-primary">
 Login
</button>
ویژگی disabled را اگر به true تنظیم کنیم، این دکمه را غیرفعال می‌کند. در اینجا متد validate تعریف شده، نال و یا یک شیء را بازگشت می‌دهد. اگر نال را بازگشت دهد، در جاوااسکریپت به false تفسیر شده و دکمه را فعال می‌کند و برعکس؛ مانند تصویر حاصل زیر:


فراخوانی setState‌های تعریف شده‌ی در متدهای رویدادگردان این فرم هستند که سبب رندر مجدد کامپوننت شده و در این بین در متد رندر، کار بررسی مجدد مقدار نهایی متد validate صورت می‌گیرد تا بر اساس آن، فعال یا غیرفعال بودن دکمه‌ی Login، مشخص شود.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-19.zip
  • #
    ‫۴ سال و ۵ ماه قبل، شنبه ۲۳ فروردین ۱۳۹۹، ساعت ۰۰:۲۲
    جهت سفارشی سازی پیام‌های خطا می‌توانید از متد messages({}) استفاده نمایید:
    schema = {
            username: Joi.string()
                .alphanum()
                .min(5)
                .max(15)
                .messages({
                    "string.empty": "نام کاربری وارد نشده است",
                    "string.min": "حداقل تعداد حروف 5 کاراکتر می‌باشد",
                    "string.max": "حداکثر تعداد حروف مجاز 15 کاراکتر می‌باشد",
                    "string.alpha": "از حروف و عدد استفاده نمایید"
                }),
    
    
            password: Joi.string()
                .min(5)
                .max(12)
                .alphanum()
                .required()
                .messages({
                    "string.empty": "رمز عبور وارد نشده است",
                    "string.min": "حداقل تعداد حروف 5 کاراکتر می‌باشد",
                    "string.max": "حداکثر تعداد حروف مجاز 15 کاراکتر می‌باشد",
                    "string.alpha": "از حروف و عدد استفاده نمایید"
                }),
    
            confirmation:
                Joi.any().valid(Joi.ref('password'))
                    .messages({
                        "any.only": "رمز عبور مطابقت ندارد"
                    })
        };
    فقط اینکه الگوی confirmpassword مشکل داره و در صورت اینکه مقدار هر دو فیلد password و confirmation یکی وارد بشه باز هم خطای عدم مطابقت رمز عبور میده ! 
    از 
    schema = schema = Joi.object().keys({  
            username: Joi.string().regex(/[a-zA-Z0-9]{3,30}/).min(3).max(30).required(),
            password: Joi.string().regex(/[a-zA-Z0-9]{3,30}/),
            confirmation: Joi.ref('password')
          })
          .with('password', 'confirmation');
    نیز به همراه تغییر کدهای Form.jsx هم استفاده کردم ولی باز هم نشد !
    • #
      ‫۴ سال و ۵ ماه قبل، شنبه ۲۳ فروردین ۱۳۹۹، ساعت ۰۰:۴۵
      این موارد را آزمایش کنید:
      password: Joi.string().min(3).max(15).required(),
      password_confirmation: Joi.any().valid(Joi.ref('password')).required().options({ language: { any: { allowOnly: 'must match password' } } })
      و یا
      confirmPassword: Joi.string().required().valid(Joi.ref('password')).options({
        language: {
          any: {
            allowOnly: '!!Passwords do not match',
          }
        } 
      })
      • #
        ‫۴ سال و ۵ ماه قبل، شنبه ۲۳ فروردین ۱۳۹۹، ساعت ۰۰:۵۰
        اینها رو هم امتحان کردم ولی با پیغام Error: "language" is not allowed در مرورگر مواجه میشم!
        • #
          ‫۴ سال و ۵ ماه قبل، شنبه ۲۳ فروردین ۱۳۹۹، ساعت ۰۱:۱۱
          همان روش messages را که عنوان کردید برای نمایش خطاها در نگارش‌های جدیدتر استفاده می‌شود. کلیدهای آن‌را هم اگر خواستید پیدا کنید روش آن به صورت زیر است:
          firstName: Joi.string().min(5).max(10).required().error(errors => {
              errors.forEach(err => {
                switch (err.type) {
                  case "any.empty":
                    err.message = "Value should not be empty!";
                    break;
                  case "string.min":
                    err.message = `Value should have at least ${err.context.limit} characters!`;
                    break;
                  case "string.max":
                    err.message = `Value should have at most ${err.context.limit} characters!`;
                    break;
                  default:
                    break;
                }
              });
              return errors;
            }),
      • #
        ‫۴ سال و ۵ ماه قبل، شنبه ۲۳ فروردین ۱۳۹۹، ساعت ۰۳:۵۰
        جهت رفع مشکل عدم تطابق گذرواژه‌ها می‌توان متد ValidateProperty رو به صورت زیر تغییر داد. فعلا تنها راه حلی بود که تونستم پیدا کنم.
         validateProperty = ({ name, value }) => {
                const userInputObject = { [name]: value };
                const propertySchema = Joi.object({ [name]: this.schema[name] });
                const { error } = propertySchema.validate(userInputObject, {
                    abortEarly: true
                });
                if (Object.keys(userInputObject)[0] === 'confirmation') {
                    if (value !== this.state.data.password)
                        return "گذرواژه‌ها با هم تطابق ندارند"
                }
                return error ? error.details[0].message : null;
            }
        فیلد confirmation در schema  هم به غیر از ست کردن Joi.ref(password) سایر validation‌ها رو میتونید بزارید.
         confirmation:
                    Joi.string()
                    .min(5)
                    .max(12)
        • #
          ‫۴ سال و ۵ ماه قبل، شنبه ۲۳ فروردین ۱۳۹۹، ساعت ۰۴:۰۹
          validateProperty یک خاصیت را تعیین اعتبار می‌کند، اما برای تطابق پسوردها، نیاز به تعیین اعتبار دو خاصیت را با هم دارد. به همین جهت برای مثال می‌توان پارامتر چهارمی را به نام correspond، در اینجا اضافه کرد تا فیلد متناظر با آن‌را هم مشخص کند:
            renderInput(name, label, type = "text", correspond) {
              const { data, errors } = this.state;
              return (
                <Input
                  name={name}
                  type={type}
                  label={label}
                  value={data[name]}
                  onChange={this.handleChange}
                  error={errors[name]}
                  correspond={correspond}
                />
              );
            }
          بعد برای نمونه، تعریف فرم ثبت نام، به صورت زیر تغییر می‌کند که در آن confirmPassword هم اضافه شده و فیلد متناظر با آن، password است:
                  <form onSubmit={this.handleSubmit}>
                    {this.renderInput("username", "Username")}
                    {this.renderInput("password", "Password", "password")}
                    {this.renderInput(
                      "confirmPassword",
                      "Confirm Password",
                      "password",
                      "password"
                    )}
                    {this.renderInput("name", "Name")}
                    {this.renderButton("Register")}
                  </form>
          در این حالت، داده‌های فرم و اعتبارسنجی‌های آن، به صورت زیر تعریف می‌شوند:
            state = {
              data: { username: "", password: "", name: "", confirmPassword: "" },
              errors: {},
            };
          
            schema = {
              username: Joi.string()
                .required()
                .email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "info"] } })
                .label("Username"),
              password: Joi.string().required().min(5).label("Password"),
              name: Joi.string().required().label("Name"),
              confirmPassword: Joi.any().valid(Joi.ref("password")).required().messages({
                "any.only": "با رمز عبور مطابقت ندارد",
              }),
            };
          و در آخر متد validateProperty بر اساس attribute سفارشی اضافه شده به نام correspond، در صورت وجود و تعریف آن، یک خاصیت پویا را به شیء داده‌های ورودی و همچنین شیء schema اضافه می‌کند که این‌ها سبب خواهند شد تا اینبار اعتبارسنجی بر روی دو فیلد صورت گیرد:
            validateProperty = ({ name, value, attributes }) => {
              const userInputObject = { [name]: value };
              const schemaMap = { [name]: this.schema[name] };
          
              if (attributes.correspond) {
                const correspondFieldName = attributes.correspond.value;
                userInputObject[correspondFieldName] = this.state.data[
                  correspondFieldName
                ];
                schemaMap[correspondFieldName] = this.schema[correspondFieldName];
              }
          
              const propertySchema = Joi.object(schemaMap);
              const { error } = propertySchema.validate(userInputObject, {
                abortEarly: true,
              });
              return error ? error.details[0].message : null;
            };