فرآیند ثبت فرم + اعتبارسنجی واقعی

Form Registration Process + Actual Validation

10 مرداد 1399

فرآیند ثبت فرم

در قسمت قبل two way binding را ایجاد کردیم تا کاربر بتواند مقادیر دلخواه خودش را در فرم وارد کند. در این قسمت قرار است فرآیند ثبت فرم را کدنویسی کنیم. اول از همه باید clickHandler قبلی خود را حذف کنیم (فایل ContactData.js):

let form = (
    <form>
        {formElementsArray.map(formElement => (
            <Input
                key={formElement.id}
                elementType={formElement.config.elementType}
                elementConfig={formElement.config.elementConfig}
                value={formElement.config.value}
                changed={(event) => this.inputChangedHandler(event, formElement.id)} />
        ))}
        <Button btnType="Success">ORDER</Button>
    </form>
);

حتما یادتان است که دکمه ی ثبت قبل از حذف clicked به شکل زیر نوشته شده بود:

<Button btnType="Success" clicked={this.orderHandler}>ORDER</Button>

حالا که این کد را حذف کردیم باید روش دیگری برای مدیریت فرم پیدا کنیم. رویدادی به نام onSubmit در React وجود دارد که روی خود فرم قرار داده می شود بنابراین متد orderHandler را به آن می دهیم:

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

درون متد orderHandler هنوز هم می خواهیم preventDefault را داشته باشیم، بنابراین به آن دست نمی زنیم. از طرفی State ما دائما بر اساس ورودی های کاربر به روز رسانی می شود (این کار را در جلسه ی قبل انجام دادیم) بنابراین برای دریافت داده های کاربر نیاز به کار خاصی نیست، بلکه همان مقادیر (value) موجود در State را یک در شیء قرار می دهیم تا آن را به سمت پایگاه داده ارسال کنیم:

orderHandler = (event) => {
    event.preventDefault();
    this.setState({ loading: true });
    const formData = {};
    for (let formElementIdentifier in this.state.orderForm) {
        formData[formElementIdentifier] = this.state.orderForm[formElementIdentifier].value;
    }
    const order = {
        ingredients: this.props.ingredients,
        price: this.props.price,
        orderData: formData
    }
    axios.post('/orders.json', order)
        .then(response => {
            this.setState({ loading: false });
            this.props.history.push('/');
        })
        .catch(error => {
            this.setState({ loading: false });
        });
}

در این کد ابتدا یک شیء خالی به نام formData ساخته ام. سپس با یک حلقه ی for درون orderForm گردش کرده ام. توجه کنید که formElementIdentifier همان مقادیر name و street و غیره است. سپس درون این حلقه، خصوصیات formElementIdentifier را به شیء formData اضافه کرده ام و مقدار آن ها را نیز از همان state گرفته ام. در نهایت formData را درون ثابت order قرار داده ام تا به همراه اطلاعات دیگر ارسال شود. اگر یک همبرگر را ثبت کنیم و به Firebase برویم، می بینیم که تمام اطلاعات به درستی درون پایگاه داده ی ما قرار گرفته اند:

فرم ثبت شده در پایگاه داده به همراه تمامی اطلاعات کاربر
فرم ثبت شده در پایگاه داده به همراه تمامی اطلاعات کاربر

اعتبارسنجی واقعی

در حال حاضر هیچ گونه اعتبارسنجی برای پاکسازی داده های کاربر نداریم و کاربران می توانند هر مقداری را که بخواهند اجرا کنند. من می خواهم همزمان با تایپ کردن کاربر و تغییر فیلد (درون متد inputChangedHandler) اعتبارسنجی نیز انجام شود. ما می خواهیم قوانینی برای اعتبارسنجی هر فیلد درون orderForm تعریف کنیم که کدهای ما بر اساس آن این فیلدها را اعتبارسنجی کنند. به طور مثال من می گویم تمام فیلدهای من required هستند یعنی حتما باید پر شوند و مقدار خالی پذیرفته نیست. از طرفی باید یک مقدار دیگر نیز داشته باشیم که مشخص کند هر فیلد valid (معتبر) است یا خیر. بنابراین این دو مقدار را به شکل زیر به تمام فیلدهای خود اضافه می کنیم:

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

همانطور که می بینید برای تک تک فیلدها یک شیء جدید به نام validation تعریف کرده ام که می گوید مقداری به نام required برابر true می باشد (یعنی پر کردن فیلد اجباری است). ممکن است بپرسید چرا برای منوی آبشاری این کار را انجام نداده ام. دلیل آن این است که کاربر حتما یک مقدار را برای این فیلد انتخاب خواهد کرد و امکان ندارد که این فیلد بدون مقدار ثبت شود بنابراین نیازی به required ندارد. همچنین مقدار valid را برای همه ی فرم ها false قرار داده ام تا به صورت پیش فرض همه ی فیلدها غیرمعتبر باشند.

حالا یک متد دیگر به نام checkValidity تعریف می کنیم تا این مقادیر را مدیریت و چک کند:

checkValidity(value, rules) {
    let isValid = false;
    
    if (rules.required) {
        isValid = value.trim() !== '';
    }

    return isValid;
}

در ابتدا یک متغیر به نام isValid ساخته ایم که به صورت پیش فرض false است. سپس در یک شرط مقدار rules.required را چک کرده ایم (rules یک پارامتر است که بعدا به این متد پاس داده می شود) تا اگر rules دارای خصوصیتی به نام required بود (یعنی فیلد ما اجباری بود) مقدار isValid تغییر کند. برای isValid گفته ایم اگر value (پارامتر پاس داده شده که مقدار تایپ شده توسط کاربر است) را trim کنیم (یعنی فضاهای خالی و اسپیس هایش را حذف کنیم) و مقدار باقی مانده برابر با یک رشته ی خالی نباشد، مقدار isValid برابر true و در غیر این صورت مقدارش برابر false است. در آخر نیز isValid را برگردانده ایم.

حالا وارد inputChangedHandler می شوم و مقدار valid را در آن تنظیم می کنم:

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);
    updatedOrderForm[inputIdentifier] = updatedFormElement;
    this.setState({ orderForm: updatedOrderForm });
}

در اینجا مقدار valid را (updatedFormElement.valid) برابر با مقدار برگشت داده شده از متد checkValidity گذاشته ام.

البته می توانیم شروط و قوانین بیشتری را تعیین کنیم. به طور مثال من برای zipCode حداقل و حداکثر تعداد رقم را تعیین می کنم:

zipCode: {
    elementType: 'input',
    elementConfig: {
        type: 'text',
        placeholder: 'ZIP Code'
    },
    value: '',
    validation: {
        required: true,
        minLength: 5,
        maxLength: 5
    },
    valid: false
}

یعنی طول کد پستی باید حتما 5 رقم باشد. بنابراین دوباره وارد checkValidity می شویم و این مقادیر را نیز چک می کنیم:

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

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

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

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

    return isValid;
}

به نظر شما این منطق کدنویسی صحیح است؟ کد بالا یک خطای بزرگ دارد! اگر آخرین موردی که چک می کنیم (در کد بالا maxLength) صحیح یا غلط باشد، کل اعتبارسنجی ما صحیح یا غلط می شود! بنابراین اگر برای ZipCode مقدار 555 را وارد کنیم با minLength صحیح نیست اما با maxLength صحیح است و در آخر isValid نیز صحیح می شود!

برای حل این مشکل باید متغیر isValid را روی true قرار دهیم و طرح زیر را پیاده سازی کنیم:

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;
}

اضافه کردن شرط isValid به انتهای تک تک این شرط ها کار جالبی انجام می دهد؛ ما می گوییم اگر قسمت اول صحیح بود (مثلا value.trim یک رشته ی خالی نبود) و همچنین isValid هم صحیح بود، مقدار isValid را صحیح قرار بده. در خط بعد اگر value.length بیشتر از minLength بود و همچنین isValid صحیح بود مقدار isValid صحیح می ماند در غیر این صورت به False برمی گردد و به همین شکل الی آخر. به همین سادگی منطق این کد را تصحیح کردیم!

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

دیدگاه‌های شما

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