تعیین قیمت و اضافه کردن دکمه‌ی Order

22 بهمن 1399
تعیین قیمت و اضافه کردن دکمه ی Order

محاسبه قیمت همبرگر

در این قسمت نوبت به بروزرسانی قیمت همبرگر و نمایش آن است. بهتر است که قیمت همبرگر را بالاتر از کنترل های کاربر قرار دهیم بنابراین به فایل BuildControls.js بروید و بالاتر از تابع map می گوییم:

const buildControls = (props) => (
    <div className={classes.BuildControls}>
        <p>Current Price: {props.price}</p>
        {controls.map(ctrl => (
            <BuildControl
                key={ctrl.label}
                label={ctrl.label}
                added={() => props.ingredientAdded(ctrl.type)}
                removed={() => props.ingredientRemoved(ctrl.type)}
                disabled={props.disabled[ctrl.type]} />
        ))}
    </div>
);

بنابراین قرار است که price یا قیمت را به صورت prop دریافت کنیم. به همین دلیل به فایل BurgerBuilder رفته و این prop را به آن پاس می دهیم:

        return (
            <Aux>
                <Burger ingredients={this.state.ingredients} />
                <BuildControls
                    ingredientAdded={this.addIngredientHandler}
                    ingredientRemoved={this.removeIngredientHandler}
                    disabled={disabledInfo}
                    price={this.state.totalPrice}
                />
            </Aux>
        );

اگر یادتان باشد قیمت همبرگر را در state تعریف کرده بودیم بنابراین می توانیم آن را با this.state دریافت کنیم. حالا اگر به مرورگر برویم و تعداد محتویات را کم و زیاد کنیم متوجه خواهیم شد که قیمت همبرگر ما اعشاری در می آید:

اعشار قیمت در همبرگر
اعشار قیمت در همبرگر

این مشکل به خاطر طبیعت جاوا اسکریپت است که باید آن را حل کنیم. ابتدا بهتر است قیمت را درون تگ های <strong> قرار دهیم تا بزرگتر و مشخص باشد:

<p>Current Price: <strong>{props.price}</strong></p>

حالا می توانیم از تابع toFixed استفاده کنیم و قیمت همبرگر را فقط تا 2 رقم اعشار تعیین کنیم:

<p>Current Price: <strong>{props.price.toFixed(2)}</strong></p>

بدین صورت قیمت در حد 2 رقم اعشار باقی می ماند و دیگر مشکل قبل را نخواهیم داشت.

ساخت دکمه ثبت سفارش

حالا باید یک Modal بسازیم تا خلاصه ای از سفارش را در اختیار ما قرار دهد. البته قبل از آن باید یک دکمه Checkout یا ثبت سفارش را ایجاد کنیم تا اگر کاربر روی آن دکمه کلیک کرد Modal برایش به نمایش در بیاید. برای شروع به فایل BuildControls.js بروید و دکمه Checkout را ایجاد کنید:

const buildControls = (props) => (
    <div className={classes.BuildControls}>
        <p>Current Price: <strong>{props.price.toFixed(2)}</strong></p>
        {controls.map(ctrl => (
            <BuildControl
                key={ctrl.label}
                label={ctrl.label}
                added={() => props.ingredientAdded(ctrl.type)}
                removed={() => props.ingredientRemoved(ctrl.type)}
                disabled={props.disabled[ctrl.type]} />
        ))}
        <button>ORDER NOW</button>
    </div>
);

همانطور که می بینید تنها یک button ساده به کد اضافه کرده ام و درون آن مقدار ORDER NOW را قرار داده ایم. حالا به فایل BuildControls.module.css بروید تا استایل هایی به این دکمه اضافه کنیم:

.OrderButton {
    background-color: #DAD735;
    outline: none;
    cursor: pointer;
    border: 1px solid #966909;
    color: #966909;
    font-family: inherit;
    font-size: 1.2em;
    padding: 15px 30px;
    box-shadow: 2px 2px 2px #966909;
}

.OrderButton:hover, .OrderButton:active {
    background-color: #A0DB41;
    border: 1px solid #966909;
    color: #966909;
}

.OrderButton:disabled {
    background-color: #C7C6C6;
    cursor: not-allowed;
    border: 1px solid #ccc;
    color: #888888;
}

.OrderButton:not(:disabled) {
    animation: enable 0.3s linear;
}

@keyframes enable {
    0% {
        transform: scale(1);
    }
    60% {
        transform: scale(1.1);
    }
    100% {
        transform: scale(1);
    }
}

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

<button className={classes.OrderButton}>ORDER NOW</button>

حالا باید چک کنیم تا کاربر همبرگر خالی را سفارش ندهد! بنابراین باید مقدار حداقلی تعیین کنیم و فقط در آن حالت دکمه ORDER NOW را از حالت disabled خارج نماییم. برای شروع یک خصوصیت جدید به state اضافه می کنم:

    state = {
        ingredients: {
            salad: 0,
            bacon: 0,
            cheese: 0,
            meat: 0
        },
        purchasable: false,
        totalPrice: 4
    }

خصوصیت purchasable به معنی «قابل خرید» است و در حالت پیش فرض مقدار false را میگیرد. حالا یک متد جدید می نویسیم:

    updatePurchaseState () {
        const ingredients = {
            ...this.state.ingredients
        };
        const sum = Object.keys(ingredients).map(igKey => {
            return ingredients[igKey];
        })
    }

در این متد ابتدا یک کپی از ingredients درون state ایجاد می کنیم. سپس می خواهیم مقادیر (تعداد محتویات) را با هم جمع کنیم و فقط در صورتی اجازه ثبت سفارش را بدهیم که تعداد کل محتویات همبرگر حداقل 1 باشد (یعنی همربرگر خالی نباشد). برای این کار ثابتی به نام sum (مجموع) ساخته ایم تا مقادیر را در خود بگیرد. سپس با استفاده از Object.keys که پیش فرض خود جاوا اسکریپت است مقدار key های ingredients را به صورت یک آرایه گرفته ایم (مثل salad و bacon و...) اما ما مقدار آن ها را می خواهیم نه key ها را... بنابراین از متد map استفاده می کنیم تا مقادیر را بگیریم. استفاده از تابع map را بار ها برایتان توضیح داده ایم.

حالا باید از تابع ()reduce استفاده کنیم تا تمام مقادیر را به یک مقدار تبدیل کنیم:

    updatePurchaseState() {
        const ingredients = {
            ...this.state.ingredients
        };
        const sum = Object.keys(ingredients).map(igKey => {
            return ingredients[igKey];
        }).reduce((sum, el) => {
            return sum + el;
        }, 0);
    }

قبلا با تابع ()reduce کار کرده ایم و می دانیم چطور کار می کند. ما تمام مقادیر را گرفته ایم و با هم جمع زده ایم و initial value را هم صفر گذاشته ایم. بنابراین بدین شکل می توانیم مجموع تمام مقادیر را به دست بیاوریم. حالا باید setState را صدا بزنیم:

    updatePurchaseState() {
        const ingredients = {
            ...this.state.ingredients
        };
        const sum = Object.keys(ingredients).map(igKey => {
            return ingredients[igKey];
        }).reduce((sum, el) => {
            return sum + el;
        }, 0);

        this.setState({ purchasable: sum > 0 });
    }

اگر sum از صفر بیشتر باشد خروجی برابر با true خواهد بود و مقدار purchasable را نیز برابر با true قرار می دهد، در غیر این صورت مقدار آن false خواهد شد. حالا باید این مقدار را به قسمت JSX پاس بدهیم:

        return (
            <Aux>
                <Burger ingredients={this.state.ingredients} />
                <BuildControls
                    ingredientAdded={this.addIngredientHandler}
                    ingredientRemoved={this.removeIngredientHandler}
                    disabled={disabledInfo}
                    purchasable={this.state.purchasable}
                    price={this.state.totalPrice}
                />
            </Aux>
        );

حالا به BuildControls.js رفته و با استفاده از اطلاعات پاس داده شده دکمه را disable می کنیم:

<button className={classes.OrderButton} disabled={!props.purchasable}>ORDER NOW</button>

علامت ! برای برعکس کردن مقدار است. اگر همبرگر purchasable (قابل خرید) باشد یعنی مقدار true دارد اما دکمه ما باید زمانی disabled شود که قابل خرید نباشد بنابراین مقدار را برعکس کرده ایم.

این کد هنوز تکمیل نشده است و این دکمه هیچ گاه فعال نخواهد شد چرا که اصلا تابع خودمان را صدا نزده ایم بنابراین به فایل BurgerBuilder.js برگردید و آن را درون متدهای addIngredientHandler و removeIngredientHandler اضافه کنید تا با هر بار تغییر محتویات همبرگر، قابل خرید بودن آن دوباره چک شود:

    addIngredientHandler = (type) => {
        const oldCount = this.state.ingredients[type];
        const updatedCount = oldCount + 1;
        const updatedIngredients = {
            ...this.state.ingredients
        };
        updatedIngredients[type] = updatedCount;

        const priceAddition = INGREDIENT_PRICES[type];
        const oldPrice = this.state.totalPrice;
        const newPrice = oldPrice + priceAddition;
        this.setState({ totalPrice: newPrice, ingredients: updatedIngredients });
        this.updatePurchaseState();
    }

    removeIngredientHandler = (type) => {
        const oldCount = this.state.ingredients[type];
        if (oldCount <= 0) {
            return;
        }
        const updatedCount = oldCount - 1;
        const updatedIngredients = {
            ...this.state.ingredients
        };
        updatedIngredients[type] = updatedCount;

        const priceDeduction = INGREDIENT_PRICES[type];
        const oldPrice = this.state.totalPrice;
        const newPrice = oldPrice - priceDeduction;
        this.setState({ totalPrice: newPrice, ingredients: updatedIngredients });
        this.updatePurchaseState();
    }

اگر الان به مرورگر بروید با مشکل عجیبی روبرو می شویم! قرار بود محدودیت محتویات همبرگر 1 باشد یعنی حداقل یک پنیر خالی، یا یک گوشت خالی و ... اما درون مرورگر تا 2 محتوا انتخاب نشود، دکمه ORDER NOW فعال نمی شود. به نظر شما دلیل آن چیست؟

همانطور که می دانید ثابت ingredients درون متد updatePurchaseState همان state قدیمی ما است:

       const ingredients = {
            ...this.state.ingredients
        };

و به دلیل طبیعت setState (که در جلسات فصل های قبل توضیح دادیم) هنگامی که updatePurchaseState را فراخوانی می کنیم، از state قدیمی استفاده می کنیم! برای حل این مشکل می توانیم مقدار updatedIngredients را به این تابع پاس بدهیم:

    addIngredientHandler = (type) => {
        const oldCount = this.state.ingredients[type];
        const updatedCount = oldCount + 1;
        const updatedIngredients = {
            ...this.state.ingredients
        };
        updatedIngredients[type] = updatedCount;

        const priceAddition = INGREDIENT_PRICES[type];
        const oldPrice = this.state.totalPrice;
        const newPrice = oldPrice + priceAddition;
        this.setState({ totalPrice: newPrice, ingredients: updatedIngredients });
        this.updatePurchaseState(updatedIngredients);
    }

    removeIngredientHandler = (type) => {
        const oldCount = this.state.ingredients[type];
        if (oldCount <= 0) {
            return;
        }
        const updatedCount = oldCount - 1;
        const updatedIngredients = {
            ...this.state.ingredients
        };
        updatedIngredients[type] = updatedCount;

        const priceDeduction = INGREDIENT_PRICES[type];
        const oldPrice = this.state.totalPrice;
        const newPrice = oldPrice - priceDeduction;
        this.setState({ totalPrice: newPrice, ingredients: updatedIngredients });
        this.updatePurchaseState(updatedIngredients);
    }

حالا تابع updatePurchaseState را متناسب با این پارامتر ورودی تغییر می دهیم:

    updatePurchaseState(ingredients) {
        const sum = Object.keys(ingredients).map(igKey => {
            return ingredients[igKey];
        }).reduce((sum, el) => {
            return sum + el;
        }, 0);

        this.setState({ purchasable: sum > 0 });
    }

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

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

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