مدیریت ثبت فرم بر اساس اعتبارسنجی (پایان فصل 9)

Form Registration Management Based on Validation

10 مرداد 1399

برای ارزشیابی فرم و ثبت آن باید کل فرم را ارزشیابی کنیم. یعنی اگر بخواهیم به کاربر اجازه بدهیم که فرم را ثبت کند، باید مطمئن شویم که تک تک فیلدها بدون خطا و معتبر (valid) هستند. برای این کار روش های مختلفی وجود دارد و روشی که من به شما می گویم تنها یکی از این روش ها است. برای شروع به state در فایل ContactData.js بروید و یک خصوصیت دیگر به نام formIsValid اضافه کنید (البته خارج از orderForm):

        deliveryMethod: {
                elementType: 'select',
                elementConfig: {
                    options: [
                        { value: 'fastest', displayValue: 'Fastest' },
                        { value: 'cheapest', displayValue: 'Cheapest' }
                    ]
                },
                value: ''
            }
        },
        formIsValid: false,
        loading: false
    }

توجه داشته باشید که formIsValid مستقیما بخشی از state بوده و درون orderForm نمی باشد. در مرحله بعد وارد متد inputChangedHandler می شویم. باید درون این متد کدی بنویسیم تا با هر بار تایپ کردن کاربر تمام فیلدها چک شوند و نتیجه را به ما اعلام کنند. اگر تمام این فیلدها معتبر بودند ما هم مقدار formIsValid را true قرار می دهیم. من این کار را به روش زیر انجام می دهم:

inputChangedHandler = (event, inputIdentifier) => {
    const updatedOrderForm = {
        ...this.state.orderForm
    };
    const updatedFormElement = {
        ...updatedOrderForm[inputIdentifier]
    };
    updatedFormElement.value = event.target.value;
    updatedFormElement.valid = this.checkValidity(updatedFormElement.value, updatedFormElement.validation);
    updatedFormElement.touched = true;
    updatedOrderForm[inputIdentifier] = updatedFormElement;

    let formIsValid = true;
    for (let inputIdentifier in updatedOrderForm) {
        formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
    }
    this.setState({ orderForm: updatedOrderForm, formIsValid: formIsValid });
}

همانطور که می بینید در ابتدا یک متغیر به نام formIsValid ساخته ام که برابر با true است. سپس با یک حلقه for بین تمام فیلدهای موجود گردش کرده ام. در این حلقه گفته شده است که اگر خصوصیت valid مربوط به یک فیلد (همان input) خاص صحیح بود و همچنین formIsValid نیز صحیح باقی مانده بود، مقدار formIsValid را برابر true قرار بده در غیر این صورت مقدار نهایی false خواهد بود. در نهایت نیز مقدار formIsValid را توسط setState به روز رسانی کرده ایم.

سوال: چرا کد بالا را به شکل زیر ننوشتیم؟

    let formIsValid = false;
    for (let inputIdentifier in updatedOrderForm) {
        formIsValid = updatedOrderForm[inputIdentifier].valid;
    }

پاسخ: اگر یادتان باشد در چند جلسه قبل توضیح دادم که با انجام این کار، تنها فیلد آخر تعیین کننده صحیح یا غلط بودن شرط بالا است! یعنی اگر فیلدهای اول و دوم غلط باشند اما فیلد آخر صحیح باشد، مقدار formIsValid در نهایت روی مورد آخر به روز رسانی شده و کل فرم true خواهد بود!

حالا می توانیم به راحتی دکمه Order را غیرفعال کنیم:

let form = (
    <form onSubmit={this.orderHandler}>
        {formElementsArray.map(formElement => (
            <Input
                key={formElement.id}
                elementType={formElement.config.elementType}
                elementConfig={formElement.config.elementConfig}
                value={formElement.config.value}
                invalid={!formElement.config.valid}
                shouldValidate={formElement.config.validation}
                touched={formElement.config.touched}
                changed={(event) => this.inputChangedHandler(event, formElement.id)} />
        ))}
        <Button btnType="Success" disabled={!this.state.formIsValid}>ORDER</Button>
    </form>
);

یعنی اگر state.formIsValid برابر false باشد (فرم ما معتبر نباشد) ما مقدار disabled را که یک attribute زبان HTML است روی true می گذاریم تا دکمه غیرفعال شود. در حال حاضر به مرورگر می رویم و به فرم خود نگاه می کنیم. شما هم متوجه مشکل شدید؟ دکمه Order بر اساس State به روز رسانی نمی شود! حتی اگر درون یکی از فیلدها مقدار اشتباهی تایپ کنیم، دکمه همچنان فعال باقی می ماند و به روز رسانی نخواهد شد. سعی کنید چند دقیقه فکر کرده و خودتان جواب این مشکل را پیدا کنید.

دلیل مشکل ما این است که از یک button عادی استفاده نکرده ایم بلکه کامپوننت Button خود را داریم و در این کامپوننت مقداری برای disabled تعریف نکرده ایم. از پوشه UI به Button و سپس Button.js بروید و این prop را به آن پاس بدهید:

const button = (props) => (
    <button
        disabled={props.disabled}
        className={[classes.Button, classes[props.btnType]].join(' ')}
        onClick={props.clicked}>{props.children}</button>
);

دوباره به مرورگر می رویم و این بار می بینیم که دکمه غیرفعال (disabled) است اما حالا مشکل دیگری داریم! اگر فرم را به طور صحیح و کامل پر کنیم، دکمه از حالت disabled خارج نمی شود و هنوز هم به ما اجازه ثبت درخواست را نمی دهد. بگذارید برای پیدا کردن رشته این مشکل به inputChangedHandler رفته و قبل از setState درون آن از console.log استفاده کنیم:

let formIsValid = true;
for (let inputIdentifier in updatedOrderForm) {
    formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
}
console.log(formIsValid);
this.setState({ orderForm: updatedOrderForm, formIsValid: formIsValid });

حالا به مروگر می رویم و همانطور که در حال تایپ در فیلدهای فرم هستیم به قسمت console مرورگر نگاه می کنیم:

formIsValid برای ما تعریف نشده است
formIsValid برای ما تعریف نشده است

همانطور که می بینید مقدار formIsValid که آن را console.log کرده ایم دائما undefined (تعریف نشده) است! به نظر شما مشکل کجا است؟ همانطور که می دانید منوی آبشاری فرم ما هیچ شیء validation ای ندارد چرا که گفتیم به اعتبارسنجی نیازی ندارد بنابراین هیچ خصوصیت valid ای ندارد.

به همین خاطر زمانی که در حلقه زیر در حال گردش هستیم:

for (let inputIdentifier in updatedOrderForm) {
    formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
}

و به مورد آخر (منوی آبشاری) می رسیم، مقدار valid ای وجود ندارد بنابراین نه true است و نه false بلکه undefined است که در زبان جاوا اسکریپت همیشه برابر با false در نظر گرفته می شود و البته هیچ وقت هم به true تبدیل نخواهد شد. ساده ترین راه حل ممکن این است که مقدار valid را به منوی آبشاری هم اضافه کنیم:

deliveryMethod: {
    elementType: 'select',
    elementConfig: {
        options: [
            {value: 'fastest', displayValue: 'Fastest'},
            {value: 'cheapest', displayValue: 'Cheapest'}
        ]
    },
    value: '',
    valid: true
}

از آنجایی که این منو هیچ وقت نیاز به اعتبارسنجی ندارد، مقدار Valid را همیشه روی true می گذاریم. حالا اگر به مرورگر بروید همه چیز طبق انتظار ما کار می کند و مشکلی نداریم به همین دلیل می توانیم دستور console.log ای را که اضافه کرده بودیم حذف نماییم.

دکمه ما استایل CSS ای برای حالت disabled ندارد بنابراین به فایل Button.module.css درون پوشه Button بروید و کلاس زیر را به آن اضافه کنید:

.Button:disabled {
    color: #ccc;
    cursor: not-allowed;
}

حالا اگر دکمه ما disabled باشد خاکستری رنگ شده و اگر موس را روی آن ببریم یک علامت ممنوع نیز نمایش داده می شود تا کاربر بفهمد که نمی تواند فرم را ثبت کند.

فرم ما تکمیل شده است، نه؟ اشتباه می کنید! اگر به مرورگر بروید و مقدار دیگری را در منوی آبشاری انتخاب کنید با یک خطا روبرو می شویم! به نظر شما دلیل این خطا چیست؟ به کد زیر نگاه کنید:

checkValidity(value, rules) {
    let isValid = true;

    if (rules.required) {
        isValid = value.trim() !== '' && isValid;
    }

    if (rules.minLength) {
        isValid = value.length >= rules.minLength && isValid
    }

    if (rules.maxLength) {
        isValid = value.length <= rules.maxLength && isValid
    }

    return isValid;
}

این کد را برای بررسی اعتبار فیلدها نوشته بودیم. مشکل اصلی از شرط اول درون این تابع است که می گوید آیا relues.required صحیح است یا خیر. به دو پارامتر این تابع نگاه کنید (value و rules). ما چه مقادیری را به جای این دو پارامتر ارسال می کنیم؟ اگر به inputChangedHanlder برویم می بینیم که این مقادیر چه مقادیری هستند:

updatedFormElement.valid = this.checkValidity(updatedFormElement.value, updatedFormElement.validation);

همانطور که قبلا هم گفتیم شیء validation برای منوی آبشاری تعریف نشده است بنابراین با تغییر این input به خطا برمی خوریم. ساده ترین راه حل آن اضافه کردن یک شیء validation خالی به این منوی آبشاری است:

deliveryMethod: {
    elementType: 'select',
    elementConfig: {
        options: [
            {value: 'fastest', displayValue: 'Fastest'},
            {value: 'cheapest', displayValue: 'Cheapest'}
        ]
    },
    value: '',
    validation: {},
    valid: true
}

به همین سادگی مشکل ما حل می شود. روش دیگری هم وجود دارد که می توانید درون تابع checkValidity یک شرط if قرار دهید و بگویید هر جایی که rule وجود نداشت (یعنی نیازی به اعتبارسنجی نبود) مقدار true را برگردان:

checkValidity(value, rules) {
    let isValid = true;

    if (!rules) {
        return true;
    }

شما می توانید از هر کدام از این روش ها استفاده کنید. من روش اول را انتخاب می کنم چون ساده تر است.

حالا فرم تکمیل شده است، نه؟ باز هم اشتباه می کنید! یک باگ کوچک در فرم ما وجود دارد. به کد زیر نگاه کنید:

deliveryMethod: {
    elementType: 'select',
    elementConfig: {
        options: [
            {value: 'fastest', displayValue: 'Fastest'},
            {value: 'cheapest', displayValue: 'Cheapest'}
        ]
    },
    value: '',
    validation: {},
    valid: true
}

این کد مربوط به منوی آبشاری ما است. اگر دقت کنید مقدار اولیه آن برابر یک رشته خالی است (value). در مرورگر ما مقدار fastest را می بینیم اما اگر کاربر این منو را تغییر ندهد، مقدار value همان رشته خالی می ماند بنابراین این مقدار به صورت یک رشته خالی به سمت سرور ارسال می شود! راه حل این مشکل بسیار ساده است:

deliveryMethod: {
    elementType: 'select',
    elementConfig: {
        options: [
            {value: 'fastest', displayValue: 'Fastest'},
            {value: 'cheapest', displayValue: 'Cheapest'}
        ]
    },
    value: 'fastest',
    validation: {},
    valid: true
}

با انجام این کار این باگ نیز حل می شود. بدین ترتیب به پایان فصل اعتبارسنجی رسیده ایم و امیدوارم که مطالب این فصل راهنمای مفیدی برای شما بوده باشند.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.