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

Advanced Redux in Burger Builder Project - Completing Reducer

15 مرداد 1399
redux پیشرفته در پروژه ی همبرگر ساز – تکمیل reducer

در قسمت قبل کدهای برنامه همبرگرساز را طوری بازنویسی کردیم که از actionCreator ها و middleware ها کمک گرفته و کدهای نامتقارن اجرا کند اما اگر مرورگر را باز کنید فعلا فقط یک Spinner برایتان نمایش داده می شود. چرا؟ به دلیل اینکه کدهای reducer را تکمیل نکرده ایم، بنابراین دریافت محتویات از سرور firebase انجام نمی شود.

اگر از جلسات قبل به یاد داشته باشید روند اجرای کار redux در برنامه های react به شکل زیر است:

جایگاه Middleware در روند کاری redux
جایگاه Middleware در روند کاری redux

یعنی ابتدا رویداد خاصی اتفاق می افتد و سپس ما یک action را بر اساس آن ارسال (یا همان dispatch) می کنیم. این action ممکن است به صورت یک شیء ساده جاوا اسکریپتی باشد که خودمان نوشته ایم و یا یک شیء جاوا اسکریپتی باشد که توسط یک تابع (که به آن ها action creator می گوییم) ارسال شود. همچنین می دانیم که منظور از dispatch شدن یا ارسال شدن، ارسال به سمت reducer است. توجه داشته باشید که action ها یک خصوصیت اجباری به نام type داشتند اما ما می توانستیم خصوصیات بیشتری را به آن ها اضافه کنیم که معمولا با نام payload شناخته می شوند. هر reducer وظیفه دارد action های dispatch شده را دریافت کرده و بر اساس منطقی که ما برایش کدنویسی می کنیم، عملیات خاصی را انجام دهد.

حالا مشکل در برنامه همبرگرساز ما چیست؟ مشکل آنجاست که action مربوط به دریافت اطلاعات (محتویات همبرگر) از firebase اصلا dispatch نشده است. همچنین ما هیچ منطقی برایreducer ننوشته ایم تا در هنگام Dispatch شدن این نوع از Action ها وارد عمل شده و محتویات همبرگر را در مرورگر و به صورت صحیح نمایش دهد. من ابتدا کدهای reducer را می نویسم و پس از تکمیل کردن دستور switch به سراغ Dispatch کردن یکی از Action creator ها به نام initIngredients می روم.

برای شروع کار وارد پوشه reducers شده و فایل burgerBuilder.js را باز کنید تا دو case جدید (برای دو action جدید) را به دستور Switch خودمان اضافه کنیم. اضافه کردن این دو مورد بسیار ساده است:

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]
            };
        case actionTypes.SET_INGREDIENTS:
            return {
                ...state,
                ingredients: ingredients,
                error: false
            };
        case actionTypes.FETCH_INGREDIENTS_FAILED:
            return {
                ...state,
                error: true
            };
        default:
            return state;
    }
};

همانطور که می بینید دو case جدید را به انتهای این دستور اضافه کرده ام. هنگامی که اطلاعات را از سرور firebase دریافت می کنیم SET_INGREDIENTS برایمان ارسال می شود بنابراین در آن می خواهیم state جدید را به همراه ingredients (محتویات همبرگر) ارسال کنیم. این محتویات را هنگام ارسال action (در جلسه قبل) به reducer می فرستادیم. همچنین در این قسمت خطاهای احتمالی را false می کنیم چرا که اگر این action دریافت شده باشد قطعا خطایی وجود نداشته است. حالت FETCH_INGREDIENTS_FAILED نیز فقط زمانی اتفاق می افتد که دریافت محتویات از سرور با خطا روبرو شده باشد بنابراین state جدید را برگردانده و error را روی true می گذاریم.

حالا می توانیم به فایل BurgerBuilder.js (درون پوشه Container) رفته و در mapStateToProps مقدار error را هم ارسال کنیم:

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

حالا که Error را از این قسمت (با استفاده از redux) پاس می دهیم، بنابراین باید آن را از redux هم دریافت کنیم. تنها جایی که از این خطا استفاده می کردیم در همین فایل بود:

        let orderSummary = null;
        let burger = this.state.error ? <p>Ingredients can't be loaded!</p> : <Spinner />;

ما باید این کد را به prop.error تغییر بدهیم:

        let burger = this.props.error ? <p>Ingredients can't be loaded!</p> : <Spinner />;

پس از ذخیره فایل به فایل دیگری به نام burgerBuilder.js (در پوشه Actions) بروید و به کد زیر نگاهی بیندازید:

export const initIngredients = () => {
    return dispatch => {
        axios.get( 'https://react-my-burger.firebaseio.com/ingredients.json' )
            .then( response => {
               dispatch(setIngredients(response.data));
            } )
            .catch( error => {
                dispatch(fetchIngredientsFailed());
            } );
    };
};

ما در هیچ جای برنامه خود، این actionCreator را dispatch نکرده ایم، یعنی هیچ درخواستی به وب برای دریافت محتویات ارسال نمی شود! بنابراین به BurgerBuilder.js برگردید (در پوشه container) و به قسمت mapDispatchToProps بروید تا initIngredients را نیز dispatch کنیم:

const mapDispatchToProps = dispatch => {
    return {
        onIngredientAdded: (ingName) => dispatch(burgerBuilderActions.addIngredient(ingName)),
        onIngredientRemoved: (ingName) => dispatch(burgerBuilderActions.removeIngredient(ingName)),
        onInitIngredients: () => dispatch(burgerBuilderActions.initIngredients())
    }
}

البته باید به فایل index.js درون پوشه actions بروید و initIngredients را نیز به آن اضافه کنید تا بتوانیم از آن درون BurgerBuilder.js استفاده کنیم:

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

حالا در همان فایل BurgerBuilder.js (درون پوشه container) به قسمت componentDidMount می رویم تا onInitIngredients را درون آن صدا بزنیم. در واقع می خواهیم زمانی که برنامه بارگذاری شد، سریعا onInitIngredients صدا زده شود و محتویات اولیه از Firebase دریافت شده و در مرورگر نمایش داده شود بنابراین:

    componentDidMount () {
        console.log(this.props); 
        this.props.onInitIngredients();
    }

بنابراین این کد onInitIngredients را صدا می زندکه باعث dispatch شدن آن از mapDispatchToProps می شود. حالا اگر کدها را ذخیره کرده و به مرورگر بروید باید با چنین صحنه ای مواجه شوید:

نمایش SET_INGREDIENTS در redux devtools
نمایش SET_INGREDIENTS در redux devtools

یعنی برنامه ما بدون خطا اجرا می شود و همچنین SET_INGREDIENTS نیز در redux devtools قابل مشاهده است. همچنین برای تست کردن خطاها در برنامه به فایل burgerBuilder.js درون پوشه actionCreator بروید و url درون axios.get را به هم بریزید تا غیر معتبر شود. حالا اگر برنامه را در مرورگر باز کنید باید خطا به صورت صحیح نمایش داده شود:

کارکرد صحیح HOC خطا یاب
کارکرد صحیح HOC خطا یاب

این وضعیت نشان می دهد که HOC ما (withErrorHandler) هنوز به درستی کار می کند. همچنین توجه داشته باشید که خدمات firebase در ایران تحریم است بنابراین اگر از ابزار رفع تحریم استفاده نکنید، با این خطا روبرو خواهید شد، حتی اگر کدهایتان صحیح باشد.

سوالی جالب: چرا در هنگام سفارش همبرگر، کاهو (salad) زیر بقیه مخلفات قرار گرفته است؟!

پاسخ: firebase به صورت خودکار و بر اساس حروف الفبا، مقدار ingredients را در پایگاه داده مرتب می کند.

ترتیب محتویات بر اساس حروف الفبا در firebase
ترتیب محتویات بر اساس حروف الفبا در firebase

از آنجایی که کاهو یا salad ما با حرف s شروع می شود پایین تر از موارد دیگر قرار گرفته است. برای حل این مشکل باید کدهایمان را به صورت دستی تغییر دهیم. به فایل burgerBuilder.js در پوشه reducers بروید و مقادیر دریافت شده از Firebase را به صورت دستی و تک تک دریافت کنید:

        case actionTypes.SET_INGREDIENTS:
            return {
                ...state,
                ingredients: {
                    salad: action.ingredients.salad,
                    bacon: action.ingredients.bacon,
                    cheese: action.ingredients.cheese,
                    meat: action.ingredients.meat
                },
                error: false
            };

با انجام این کار دیگر مشکلی از نظر  ترتیب موارد نمایش داده شده وجود ندارد.

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

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

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