تصحیح قیمت همبرگر در Redux

Hamburger Price Correction in Redux

13 مرداد 1399
تصحیح قیمت همبرگر در redux

در قسمت قبل توانستیم با استفاده از redux تعداد مخلفات را کم و زیاد کنیم اما قیمت (price) همراه با تغییر محتویات همبرگر تغییری نمی کند. در این جلسه می خواهیم محاسبات قیمت همبرگر را نیز از طریق redux پاس بدهیم. فعلا آن را در فایل reducer.js داریم اما هیچ تغییری روی آن اعمال نمی شود:

const initialState = {
    ingredients: {
        salad: 0,
        bacon: 0,
        cheese: 0,
        meat: 0
    },
    totalPrice: 4
};

همه ما می دانیم که قیمت با اضافه شدن یا کم شدن محتویات تغییر می کند، بنابراین نیازی به داشتن action های اختصاصی ندارد و از طریق همان action های اضافه کردن یا کم کردن محتویات همبرگر قابل تغییر است. گرچه شما می توانید یک action جدید برای محاسبه قیمت تعریف کنید و مشکلی ایجاد نخواهد کرد. من از همان روش اول جلو می روم. در حال حاضر reducer ما چنین کدی را دارد:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.ADD_INGREDIENT:
            return {
                ...state,
                ingredients: {
                    ...state.ingredients,
                    [action.ingredientName]: state.ingredients[action.ingredientName] + 1
                }
            };
        case actionTypes.REMOVE_INGREDIENT:
            return {
                ...state,
                ingredients: {
                    ...state.ingredients,
                    [action.ingredientName]: state.ingredients[action.ingredientName] - 1
                }
            };
        default:
            return state;
    }
};

نقشه من این است که فرآیند اضافه شدن یا کم شدن قیمت را در همین دستور محاسبه کنم. برای انجام چنین کاری نیاز به داشتن قیمت هر کدام از مواد همبرگر (گوشت و نان و ...) داریم تا بتوانیم با اضافه شدن هر کدام از آن ها قیمت را به شکل صحیح و مناسبی افزایش دهیم. این قیمت ها را در ابتدای فایل BurgerBuilder.js داریم:

const INGREDIENT_PRICES = {
    salad: 0.5,
    cheese: 0.4,
    meat: 1.3,
    bacon: 0.7
};

این ثابت را از BurgerBuilder کات کرده و به reducer.js انتقال دهید. حالا می توانیم به شکل زیر قیمت را نیز تغییر دهیم:

const reducer = ( state = initialState, action ) => {
    switch ( action.type ) {
        case actionTypes.ADD_INGREDIENT:
            return {
                ...state,
                ingredients: {
                    ...state.ingredients,
                    [action.ingredientName]: state.ingredients[action.ingredientName] + 1
                },
                totalPrice: state.totalPrice + INGREDIENT_PRICES[action.ingredientName]
            };
        case actionTypes.REMOVE_INGREDIENT:
            return {
                ...state,
                ingredients: {
                    ...state.ingredients,
                    [action.ingredientName]: state.ingredients[action.ingredientName] - 1
                },
                totalPrice: state.totalPrice - INGREDIENT_PRICES[action.ingredientName]
            };
        default:
            return state;
    }
};

یعنی قیمت جدید برابر با قیمت قدیمی به علاوه قیمت هر کدام از مواد اضافه شده است. همانطور که مشخص است نام مواد را از ingedientName در action خود دریافت کرده ایم.

در مرحله بعد باید فایل BurgerBuilder.js برویم چرا که با حذف ثابت INGREDIENT_PRICES کد را با خطا مواجه کرده ایم. در این فایل دو متد به نام های addIngredientHandler و removeIngredientHandler وجود دارند که کارشان حذف و اضافه کردن محتویات به همبرگر بود (زمانی که از state درون خود react استفاده می کردیم) و از ثابت INGREDIENT_PRICES استفاده می کردند:

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

حالا که ما این ثابت را از فایل BurgerBuilder.js حذف کرده ایم دیگر تعریف نشده خواهد بود و به خطا می خوریم اما از آنجایی که دیگر نیازی به این متدها نداریم می توانید هر دو متد را از فایل به طور کامل حذف کنید.

اگر کد ها را ذخیره و به مرورگر بروید متوجه خواهید شد که با حذف کردن و اضافه کردن مواد همبرگر، قیمت هیچ تغییری نمی کند. به نظر شما مشکل از کجاست؟ مشکل اینجاست که ما قیمت را در State سراسری خود تنظیم کرده ایم اما برای نمایش قیمت در مرورگر از این State استفاده نمی کنیم.

در واقع اگر به انتهای فایل BurgerBuilder.js توجه کنیم متوجه می شویم که فقط قیمت محتویات را از State سراسری گرفته ایم:

const mapStateToProps = state => {
    return {
        ings: state.ingredients
    };
}

راه حل این مشکل نیز بسیار آسان است. باید ابتدا قیمت را نیز از State سراسری گرفته و سپس آن را در مرورگر نمایش دهیم:

const mapStateToProps = state => {
    return {
        ings: state.ingredients,
        price: state.totalPrice
    };
}

حالا که price را از state سراسری دریافت کرده ایم می توانید totalPrice را از درون State قدیمی (در ابتدای همین فایل) حذف کنید. در مرحله بعد می توانیم به متد render برویم و قیمت را در آن جا به روز رسانی کنیم. چطور؟ هر جایی که state.totalPrice می بینید باید آن را تبدیل به props.price می کنیم:

if (this.props.ings) {
    burger = (
        <Aux>
            <Burger ingredients={this.props.ings} />
            <BuildControls
                ingredientAdded={this.props.onIngredientAdded}
                ingredientRemoved={this.props.onIngredientRemoved}
                disabled={disabledInfo}
                purchasable={this.updatePurchaseState(this.props.ings)}
                ordered={this.purchaseHandler}
                price={this.props.price} />
        </Aux>
    );
    orderSummary = <OrderSummary
        ingredients={this.props.ings}
        price={this.props.price}
        purchaseCancelled={this.purchaseCancelHandler}
        purchaseContinued={this.purchaseContinueHandler} />;
}

در قسمت بالا دو مورد price در BuildControls و price در OrderSummary را تصحیح کرده ام. البته یک قسمت دیگر نیز وجود دارد که از state.totalPrice استفاده می کند (purchaseContinueHandler) اما فعلا با آن کاری نداریم چرا که می خواهم در جلسات بعدی کد های آن را به طور کامل حذف کنم.

فعال کردن دکمه ORDER NOW

هنوز دکمه ORDER NOW با مشکل مواجه است و با اصلا فعال نمی شود. معیار ما برای فعال کردن آن خصوصیت purchasable در BuildControls.js بود و اگر prop.purchasable برابر false باشد دکمه نیز غیرفعال می ماند:

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 
            className={classes.OrderButton}
            disabled={!props.purchasable}
            onClick={props.ordered}>ORDER NOW</button>
    </div>
);

بنابراین باید purchasable را نیز از state جدیدمان مدیریت کنیم. ابتدا به فایل BurgerBuilder.js بروید و purchasable را از state قبلی حذف کنید. حالا به متد updatePurchaseState نگاهی بیندازید. این متد وظیفه فعال کردن و غیرفعال کردن دکمه ما را دارد اما به جای حذف آن می توانیم ویرایش کوچکی برایش انجام دهیم:

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

ویرایش ما بدین شکل است که از همان منطق قبلی استفاده کرده ایم اما در نهایت گفته ایم که اگر شرط sum بزرگ تر از صفر صحیح بود، مقدار true و در غیر این صورت مقدار false را برگردان. حالا به متد render در همین فایل می رویم و در قسمتی که purchasable را تعیین کرده ایم، می گوییم:

if (this.props.ings) {
    burger = (
        <Aux>
            <Burger ingredients={this.props.ings} />
            <BuildControls
                ingredientAdded={this.props.onIngredientAdded}
                ingredientRemoved={this.props.onIngredientRemoved}
                disabled={disabledInfo}
                purchasable={this.updatePurchaseState(this.props.ings)}
                ordered={this.purchaseHandler}
                price={this.props.price} />
        </Aux>
    );
    // بقیه کد ها

سوال: همانطور که می دانید به اضافه کردن پرانتز به جلوی نام این متد، کاری کرده ایم که به محض render و به صورت خودکار اجرا شود. چرا متد updatePurchaseState را از درون props صدا زده ایم؟

پاسخ: متد updatePurchaseState وظیفه برگرداندن وضعیت دکمه ORDER NOW را دارد بنابراین باید با هر بار render شدن کامپوننت ها یک بار اجرا شود تا مطمئن شویم همبرگر قابل خرید است یا خیر.

به همین سادگی دکمه ORDER NOW را نیز تصحیح کرده ایم و می توانید وضعیت آن را در مرورگر خود تست کنید. در قسمت بعد کامپوننت های Ckeckout و ContactData را تصحیح خواهیم کرد تا با state سراسری کار کنند.

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

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

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