مدیریت ورودی های کاربر

Manage User Inputs

22 بهمن 1399

در قسمت قبل فرم خود را کامل کردیم و حالا نوبت به دریافت داده های وارد شده توسط کاربر است. در حال حاضر داده ها را به صورت دستی روی مقادیر پیش فرضی تعریف کرده ایم تا بتوانیم آن را تست کنیم اما در برنامه ی واقعی این داده ها از سمت کاربر می آید. برای شروع وارد کامپوننت Input.js شوید و در دستور switch به هر کدام از کامپوننت ها یک متد onChange بدهید:

switch (props.elementType) {
    case ('input'):
        inputElement = <input
            className={classes.InputElement}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
        break;
    case ('textarea'):
        inputElement = <textarea
            className={classes.InputElement}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
        break;
    case ('select'):
        inputElement = (
            <select
                className={classes.InputElement}
                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={classes.InputElement}
            {...props.elementConfig}
            value={props.value}
            onChange={props.changed} />;
}

مشخصا من نمی خواهم متد مربوط به این event را در همین فایل بنویسم بنابراین آن را از طریق props به Input.js پاس داده ام. من نام این متد را changed انتخاب کرده ام اما شما می توانید هر نام دیگری را انتخاب کنید. حالا باید به ContactData.js برویم و این props را به فرم خود پاس بدهیم:

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={this.inputChangedHandler} />
        ))}
           <Button btnType="Success" clicked={this.orderHandler}>ORDER</Button>
    </form>
);

فعلا آن را خالی می گذارم و بیرون از تابع Render متد changed را تعریف می کنم و نام آن را inputChangedHandler می گذارم:

inputChangedHandler = (event) => {
    console.log(event.target.value)
}

من در ابتدا مقدار تایپ شده درون فیلدهای فرم را console.log می کنم تا مطمئن شوم این متد به شکل صحیحی به کامپوننت من متصل شده باشد. اگر به مرورگر بروید و هر مقداری را در فیلدها تایپ کنید، آن مقدار Console.log می شود بنابراین کار ما تا اینجا صحیح بوده است اما به مشکل عجیبی برمی خوریم. هنگامی که در فیلدی تایپ می کنیم هیچ چیزی در آن نوشته نمی شود بلکه فقط در Console.log نمایش داده می شود. به نظر شما مشکل کجاست؟

مشکل عدم وجود two way binding است! یعنی ما درون state خود برای هر فیلد مقدار value را داریم که یک رشته ی خالی است و با تایپ کردن کاربر تغییر نمی کند! مثال:

orderForm: {
    name: {
        elementType: 'input',
        elementConfig: {
            type: 'text',
            placeholder: 'Your Name'
        },
        value: ''
    },
}

به همین دلیل هر چقدر هم که در فیلدها تایپ کنیم این رشته تغییر نخواهد کرد. برای حل این مشکل به غیر از event درون متد inputChangedHandler به یک inputIdentifier (مشخص کننده ی input) نیاز داریم تا بدانیم کدام شیء را از state بگیریم و به روز رسانی کنیم:

inputChangedHandler = (event, inputIdentifier) => {
    
}

از آنجایی که نیازی به console.log نبود آن را حذف کردم. در قدم بعدی باید این دو پارامتر را در قسمت فرم نیز ذکر کنیم:

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" clicked={this.orderHandler}>ORDER</Button>
    </form>
);

مقدار formElement.id را به یاد دارید؟ id همان مقداری id است که برای ساخت فرم در حلقه ی For خود از آن استفاده کردیم:

const formElementsArray = [];
for (let key in this.state.orderForm) {
    formElementsArray.push({
        id: key,
        config: this.state.orderForm[key]
    });
}

اگر یادتان باشد این key همان نام های اشیاء ما بود (zipCode و country و name و street و ...).

در مرحله ی بعد باید به متد inputChangedHandler برگردیم و مقادیر Value در orderForm (درون state) را تغییر دهیم. ما نمی توانیم این مقادیر را به صورت مستقیم تغییر دهیم (در فصل های اول چندین بار این مسئله را توضیح دادیم) بنابراین باید ابتدا یک کپی از state بگیریم و سپس آن را تغییر دهیم:

inputChangedHandler = (event, inputIdentifier) => {
    const updatedOrderForm = {
        ...this.state.orderForm
    };
}

احتمالا فکر می کنید تمام orderForm را کپی کرده اید اما اپراتور Spread وارد اشیاء درون orderForm نمی شود. بگذارید بیشتر توضیح بدهم. در حال حاضر شکل orderForm به صورت زیر است:

state = {
    orderForm: {
        name: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Your Name'
            },
            value: ''
        },
        street: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Street'
            },
            value: ''
        },
        //بقیه ی کد ها //

اگر دقت کنید درون orderForm اشیائی داریم (name و street و ...) داخل همین اشیاء، اشیاء دیگری نیز وجود دارد (مثل elementConfig). به دلیل وجود اشیاء تو در تو، اپراتور spread که در بالا گذاشته ایم از شیء ما کپی می گیرد اما کپی اصلی نیست بلکه فقط یک pointer به آن را تنظیم می کند که همان مشکل قبلی ما است. برای حل این مشکل از این اشیاء به صورت deep (به معنی «عمیق») کپی می گیریم:

inputChangedHandler = (event, inputIdentifier) => {
    const updatedOrderForm = {
        ...this.state.orderForm
    };
    const updatedFormElement = {
        ...updatedOrderForm[inputIdentifier]
    };
}

حالا هر کدام از مقادیر name و street و غیره را درون updatedFormElement خواهیم داشت که کاملا مستقل از state هستند. بقیه ی کار بسیار آسان است:

inputChangedHandler = (event, inputIdentifier) => {
    const updatedOrderForm = {
        ...this.state.orderForm
    };
    const updatedFormElement = {
        ...updatedOrderForm[inputIdentifier]
    };
    updatedFormElement.value = event.target.value;
    updatedOrderForm[inputIdentifier] = updatedFormElement;
    this.setState({ orderForm: updatedOrderForm });
}

ما مقدار Value هر کدام از فیلدها را برابر با event.target.value (مقدار تایپ شده توسط کاربر) گذاشته ایم. سپس آن را در فیلدهای موجود در updatedOrderForm تنظیم کرده ایم. در نهایت با استفاده از دستور setState می توانیم مقدار updatedOrderForm را روی orderForm تنظیم کنیم، بدون آنکه مشکلی داشته باشیم.

حالا two way binding ما به شکل کامل کار می کند و با مراجعه به مرورگر می بینید که با تایپ درون هر کدام از فیلدها مقدار تایپ شده ی ما نمایش داده می شود. در قسمت بعدی در رابطه با نحوه ی ثبت فرم و همچنین اعتبارسنجی بیشتر فرم ها صحبت خواهیم کرد.

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

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