اعلام نتیجه اعتبارسنجی به کاربر

Announcing the Validation Result to the User

22 بهمن 1399

در حال حاضر فرم ما یک سیستم اعتبارسنجی ساده دارد که از ارسال اطلاعات خالی جلوگیری می کند اما هیچ خطایی به کاربر نمایش نمی دهد. به طور مثال اگر فیلدی خالی باشد و کاربر روی دکمه ثبت فرم کلیک کند، فرم ارسال نمی شود اما هیچ اطلاعاتی نیز به کاربر ارسال نخواهد شد. ما می خواهیم کاری کنیم که با رد شدن اعتبارسنجی برای یک فیلد، کلاس CSS خاصی روی آن فیلد تنظیم شود تا کاربر بفهمد مشکل از آن فیلد است.

در حال حاضر کلاس های هر فیلد در دستور switch درون Input.js تعیین می شود:

switch ( props.elementType ) {
    case ( 'input' ):
        inputElement = <input
            className={classes.InputElement}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
        break;
    case ( 'textarea' ) ...

این کلاس ها پویا نبوده و همیشه ثابت هستند. برای حل این مشکل قبل از دستور switch یک ثابت تعریف کنید تا کلاس CSS پیش فرض را بگیرد:

const input = ( props ) => {
    let inputElement = null;
    const inputClasses = [classes.InputElement];

حالا تمام کلاس های تعیین شده در دستور switch را روی inputClasses تنظیم می کنم:

switch (props.elementType) {
    case ('input'):
        inputElement = <input
            className={inputClasses.join(' ')}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
        break;
    case ('textarea'):
        inputElement = <textarea
            className={inputClasses.join(' ')}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
        break;
    case ('select'):
        inputElement = (
            <select
                className={inputClasses.join(' ')}
                value={props.value}
                onChange={props.changed}>
                {props.elementConfig.options.map(option => (
                    <option key={option.value} value={option.value}>
                        {option.displayValue}
                    </option>
                ))}
            </select>
        );
        break;
    default:
        inputElement = <input
            className={inputClasses.join(' ')}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
}

همانطور که می بینید برای کلاس ها inputClasses را گذاشته ام و سپس از دستور join استفاده کرده ام تا کلاس ها را با یک فاصله (اسپیس) از یکدیگر جدا کنم. در مرحله بعد می خواهم با یک شرط if تعیین کنم که اگر فیلدی نامعتبر بود کلاس خاصی بگیرد بنابراین بالای دستور switch می نویسم:

if (props.invalid) {
    inputClasses.push(classes.Invalid);
}

حالا اگر prop ای به نام invalid به ما پاس داده شود، کلاسی به نام Invalid به inputClasses اضافه خواهد شد. البته ما هنوز این کلاس را تعریف نکرده ایم بنابراین وارد فایل input.module.css شوید و کلاس زیر را به آن اضافه کنید:

.Invalid {
    border: 1px solid red;
    background-color: #FDA49A;
}

در مرحله بعد باید props.invalid را به input.js پاس بدهیم بنابراین وارد فایل ContactData.js شده و به قسمت تعریف form بروید و به روش من عمل کنید:

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}
                changed={(event) => this.inputChangedHandler(event, formElement.id)} />
        ))}
        <Button btnType="Success">ORDER</Button>
    </form>
);

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

قرمز بودن تمام فیلد ها در بارگذاری اولیه
قرمز بودن تمام فیلدها در بارگذاری اولیه

مشکل اینجاست که یک فرم نباید از همان اول قرمز باشد چرا که ظاهر بدی پیدا می کند. همچنین منوی آبشاری ما همیشه حاشیه قرمز دارد که باید ابتدا آن را حل کنیم. به input.js برگردید و شرط if را مثل من تغییر دهید:

if (props.invalid && props.shouldValidate) {
    inputClasses.push(classes.Invalid);
}

shouldValidate یک prop است که تعیین می کند آیا فیلد ما نیاز به اعتبارسنجی دارد یا خیر؟ باید به ContactData.js برویم و آن را به form اضافه کنیم:

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}
                changed={(event) => this.inputChangedHandler(event, formElement.id)} />
        ))}
        <Button btnType="Success">ORDER</Button>
    </form>
);

ما درون shouldValidate به دنبال شیء validation می گردیم. آیا یادتان است که این شیء کجا قرار داشت؟ بله در formOder درون state ما برای بعضی از فیلدها validation را تعریف کرده بودیم:

orderForm: {
    name: {
        elementType: 'input',
        elementConfig: {
            type: 'text',
            placeholder: 'Your Name'
        },
        value: '',
        validation: {
            required: true
        },
        valid: false,
    },

در این قسمت، شیء Validation را می بینید که دارای خصوصیت required است. ما به دنبال همین شیء می گردیم. چرا؟ به دلیل اینکه منوی آبشاری (drop-down) نیازی به اعتبارسنجی ندارد و ما هم برایش validation را تعریف نکرده ایم، بنابراین می توانیم از آن برای شناسایی فیلدهای اینچنینی استفاده کنیم.

حالا مشکل منوی آبشاری حل شده است و دیگر قرمز نیست اما بقیه فیلدهای فرم همگی قرمز هستند. یک روش خوب برای پیاده سازی این نوع از اعتبارسنجی ها این است که در ابتدا هیچ فیلدی قرمز نباشد اما اگر کاربر یکی از فیلدها را فعال کرد (مثلا با موس روی آن کلیک کرد یا با گوشی همراه آن را لمس کرد) آن فیلد اعتبار سنجی شده و قرمز شود تا زمانی که کاربر مقدار صحیح را وارد فیلد کند. برای انجام این کار به state در فایل ContactData می روم و درون orderForm و برای هر کدام از خصوصیت ها (مثل name و street و ...) یک مقدار جدید به نام touched قرار می دهم که در حالت پیش فرض false باشد:

state = {
    orderForm: {
        name: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Your Name'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        street: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Street'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        zipCode: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'ZIP Code'
            },
            value: '',
            validation: {
                required: true,
                minLength: 5,
                maxLength: 5
            },
            valid: false,
            touched: false
        },
        country: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Country'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        email: {
            elementType: 'input',
            elementConfig: {
                type: 'email',
                placeholder: 'Your E-Mail'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        deliveryMethod: {
            elementType: 'select',
            elementConfig: {
                options: [
                    { value: 'fastest', displayValue: 'Fastest' },
                    { value: 'cheapest', displayValue: 'Cheapest' }
                ]
            },
            value: ''
        }
    },
    loading: false
}

همانطور که می بینید touched به همه این فیلدها (به جز منوی آبشاری) اضافه شده است. حالا باید بگوییم تابع checkValidity فقط هنگامی اجرا شود که کاربر فیلد مورد نظر را فعال کرده باشد. به عبارت دیگر اعتبارسنجی فقط زمانی رخ دهد که یکی از فیلدها فعال شده باشد. در قدم اول باید وارد متد inputChangedHandler شویم و درون آن مقدار touched را روی true بگذاریم. چرا؟ به دلیل اینکه inputChangedHandler تنها زمانی اجرا می شود که یک input تغییر کند و اگر input تغییر کند، قطعا فعال هم شده است:

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;

    // بقیه کد ها

سپس باید touched را به input.js بفرستیم بنابراین در همان قسمت form آن را به عنوان یک prop ارسال می کنیم:

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">ORDER</Button>
    </form>
);

حالا وارد فایل Input.js شده و شرط if را به شکل زیر تغییر می دهیم:

if (props.invalid && props.shouldValidate && props.touched) {
    inputClasses.push(classes.Invalid);
}

اکنون با مراجعه به مرورگر می توانیم این کد را تست کنیم. در هنگام بارگذاری اولیه صفحه هیچ فیلدی قرمز نیست و فقط زمانی که ما شروع به تایپ کنیم قرمز می شود تا جایی که مقدار صحیح را وارد کنیم و فیلد دوباره از حالت قرمز بودن خارج شود. مشکل قرمز بودن حاشیه منوی آبشاری هم که حل شده است بنابراین کد های ما کاملا صحیح هستند. در قسمت بعد می خواهیم کاری کنیم که اگر یکی از فیلدهای ما نامعتبر یا invalid بود، کاربر اجازه ثبت کردن فرم را نداشته باشد و دکمه Order از کار بیفتد.

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

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