Redux پیشرفته در پروژه همبرگرساز – تکمیل order.js

Advanced Redux in Burger Builder Project - Completing order.js

22 بهمن 1399
redux پیشرفته در پروژه ی همبرگر ساز – تکمیل order.js

در قسمت قبل روی actionCreator ها کار کردیم و حالا می توانیم action های خود را dispatch کنیم اما هنوز reducer مرتبط با آن ها را تکمیل نکرده ایم. برای شروع به فایل order.js (در پوشه reducers) بروید و اول از همه actionTypes خود را وارد این فایل کنید:

import * as actionTypes from '../actions/actionTypes';

سپس مثل همیشه یک reducer ساده تعریف می کنیم:

const reducer = (state, action) => {

}

حالا باید یک initialState یا state ابتدایی داشته باشیم. من می خواهم تمام سفارشات خود را به صورت یک آرایه داشته باشم، همچنین می خواهم بدانم که آیا در روند ثبت سفارش هستیم یا اینکه ثبت سفارش کامل شده است. با این حساب می توان گفت:

const initialState = {
    orders: [],
    loading: false
};

const reducer = (state = initialState, action) => {

}

خصوصیت loading نشان دهنده این است که در حال ثبت سفارش هستیم یا ثبت سفارش تمام شده است. با تعریف این خصوصیت به مشکل بر می خوریم. چرا؟ به دلیل اینکه در actionCreator های خود برای سفارشات (فایل order.js در پوشه actions) فقط دو حالت success و fail (موفقیت آمیز بودن یا نبودن سفارش) را مدیریت می کنیم و تابع purchaseBurgerStart چیزی را dispatch نمی کند تا آن را در reducer دریافت کنیم. بنابراین برای بروز رسانی loading در redux به یک action دیگر نیاز خواهیم داشت که بعدا به سراغش می رویم.

فعلا روی action های موجود تمرکز می کنیم و از دستور switch همیشگی استفاده می کنیم:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.PURCHASE_BURGER_SUCCESS:
            return {};
        case actionTypes.PURCHASE_BURGER_FAIL:
            return {};
        default:
            return state;
    }
}

export default reducer;

همانطور که مشاهده می کنید دو case موفقیت آمیز بودن و خطا در سفارش را داریم و case سوم که حالت پیش فرض است state فعلی را بر می گرداند. این حالت پیش فرض بسیار مهم است چرا که ما می خواهیم چند reducer را با هم ترکیب کنیم، بنابراین اگر action ای داشته باشیم که با یکی از این reducer ها کاری نداشته باشد، آن reducer باید بتواند state فعلی برنامه را برگرداند تا به خطا بر نخوریم. در نهایت نیز reducer خود را export کرده ام تا بعدا از آن استفاده کنیم.

حالا باید روی منطق این کد کار کنیم. در هنگام موفقیت آمیز بودن درخواست، می خواهم سفارش را در آرایه order ثبت کنم و همچنین loading را روی حالت false بگذارم چرا که در این حالت سفارش قطعا تمام شده است. بنابراین ابتدا state قبلی را کپی کرده و loading را روی false می گذارم. سپس باید سفارشات را درون آرایه orders قرار بدهم و برای این کار از دستور concat استفاده می کنم تا یک آرایه جدید به من برگردانده شود نه اینکه همان آرایه قبلی کپی شود:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.PURCHASE_BURGER_SUCCESS:
            return {
                ...state,
                loading: false,
                orders: state.orders.concat();
            };
        case actionTypes.PURCHASE_BURGER_FAIL:
            return {};
        default:
            return state;
    }
}

Order ای که قرار است در این قسمت ثبت شود همان order ای است که در فایل order.js درون پوشه actions ارسال می شود، یعنی این کد:

export const purchaseBurgerSuccess = (id, orderData) => {
    return {
        type: actionTypes.PURCHASE_BURGER_SUCCESS,
        orderId: id,
        orderData: orderData
    };
};

همانطور که می بینید orderID و orderData دو موردی هستند که هنگام موفقیت در ثبت سفارش برای ما ارسال می شوند. من باید هر دو مورد را درون یک شیء بگذارم تا از آن ها استفاده کنم. بنابراین به reducer خود برگردید و شیء جاوا اسکریپتی جدیدی تعریف کنید. من نامش را newOrder می گذارم و در آن تمام خصوصیات orderData را کپی می کنم (همان orderData ای که در کد بالا می بینیم و به سرور ارسال می شود). همچنین خصوصیت id را نیز در همین قسمت دریافت می کنم بنابراین:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.PURCHASE_BURGER_SUCCESS:
            const newOrder = {
                ...action.orderData,
                id: action.orderId
            }
            return {
                ...state,
                loading: false,
                orders: state.orders.concat(newOrder);
            };
        case actionTypes.PURCHASE_BURGER_FAIL:
            return {};
        default:
            return state;
    }
}

در نهایت همین newOrder را به concat داده ام تا در قسمت orders قرار بگیرد. این منطق کدهای ما هنگام موفقیت آمیز بودن درخواست است اما برای حالت Fail و شکست درخواست چه کار باید کرد؟ در این حالت باید state قبل از شکست را برگردانیم تا اگر کاربر خواست دوباره تلاش کند، سفارشش حذف نشده باشد و مجبور به ثبت سفارش جدید نشود. همچنین وضعیت loading باید روی false باشد چرا که اگر ثبت سفارش شکست خورده باشد دیگر در حال ثبت سفارش نبوده و کارمان تمام شده است:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.PURCHASE_BURGER_SUCCESS:
            const newOrder = {
                ...action.orderData,
                id: action.orderId
            }
            return {
                ...state,
                loading: false,
                orders: state.orders.concat(newOrder);
            };
        case actionTypes.PURCHASE_BURGER_FAIL:
            return {
                ...state,
                loading: false
            };
        default:
            return state;
    }
}

حالا باید روی loading کار کنیم و البته بعضی از کدها را از جلسه قبل تصحیح کنیم. برای تعیین وضعیت loading باید یک actionType جدید داشته باشیم بنابراین به actionTypes.js بروید و کد زیر را به آن اضافه کنید:

export const PURCHASE_BURGER_START = 'PURCHASE_BURGER_START';

سپس به فایل order.js درون پوشه actions بروید. ما در این فایل تابعی به نام purchaseBurgerStart را داریم اما این تابع حاوی کدهای نامتقارن ما است و action ای را برنمی گرداند بنابراین باید آن را تغییر دهیم. ابتدا یک actionCreator جدید به نام purchaseBurgerStart می سازیم و تابع قبلی را به purchaseBurger تغییر نام می دهیم. purchaseBurgerStart به شکل زیر خواهد بود:

export const purchaseBurgerStart = () => {
    return {
        type: actionTypes.PURCHASE_BURGER_START
    };
};

از آنجایی که تابع قبلی را به purchaseBurger تغییر نام دادیم باید به فایل index.js درون پوشه actions برویم و دستور export آن را اصلاح کنیم:

export {
    addIngredient,
    removeIngredient,
    initIngredients
} from './burgerBuilder';
export {
    purchaseBurger
} from './order';

حالا در order.js (در پوشه actions (purchaseBurger را به شکل زیر تصحیح می کنیم:

export const purchaseBurgerStart = () => {
    return {
        type: actionTypes.PURCHASE_BURGER_START
    };
};

export const purchaseBurger = ( orderData ) => {
    return dispatch => {
        dispatch( purchaseBurgerStart() );
        axios.post( '/orders.json', orderData )
            .then( response => {
                console.log( response.data );
                dispatch( purchaseBurgerSuccess( response.data, orderData ) );
            } )
            .catch( error => {
                dispatch( purchaseBurgerFail( error ) );
            } );
    };
};

یعنی با استفاده از Dispatch تابع purchaseBurgerStart را صدا زده ایم. در مرحله بعد به فایل ContactData.js می رویم و در انتهای فایل به جای dispatch کردن purchaseBurgerStart تابع purchaseBurger را dispatch می کنیم:

const mapDispatchToProps = dispatch => {
    return {
        onOrderBurger: (orderData) => dispatch(actions.purchaseBurger(orderData))
    };
};

همچنین state همین فایل (state داخلی خود react) باید loading را حذف کنیم چرا که حالا از طریق redux آن را مدیریت می کنیم:

            }
        },
        formIsValid: false
    }

این کد قسمت انتهایی state فایل ContactData.js می باشد که loading را از آن حذف کرده ام. کد شما نیز باید به همین شکل باشد. سپس به mapStateToProps در انتهای همین فایل می رویم و error را از طریق redux پاس می دهیم:

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

البته بعدا reducer های خودمان را ادغام می کنیم و باید این کد را دوباره تصحیح کنیم اما فعلا مشکلی نیست. حالا می توانیم دستور زیر را:

        if ( this.state.loading ) {
            form = <Spinner />;
        }

به این شکل تغییر دهم:

        if ( this.props.loading ) {
            form = <Spinner />;
        }

در مرحله نهایی mapDispatchToProps را به connect (در انتهای همین فایل) می دهیم:

export default connect(mapStateToProps, mapDispatchToProps)(withErrorHandler(ContactData, axios));

سپس وارد order.js در پوشه reducers می شویم تا کدهای باقی مانده را تکمیل کنیم، یعنی case جدیدی برای شروع فرآیند ثبت سفارش ایجاد می کنیم:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.PURCHASE_BURGER_START:
            return {
                ...state,
                loading: true
            }
            case actionTypes.PURCHASE_BURGER_SUCCESS:
                // بقیه کدها

مشخصا در این کد باید حالت loading روی true باشد. در حال حاضر هیچ قسمتی از برنامه ما کار نمی کند و کدها هنوز نیاز به کار دارند. در جلسه بعد بیشتر روی کدها کار خواهیم کرد.

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

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

زهرا
08 خرداد 1401
سلام واقعا عالی بود، خیلی مفیدتر از فیلم‌های تو نت بود ک فقط وقت ادمو میگیره. کد تکمیل شده پروژه همبرگر ساز رو از کجا بگیریم، امکانش هست برام ایمیل کنید

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