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

Advanced Redux in Burger Builder Project - Receiving Orders from Firebase

22 بهمن 1399
redux پیشرفته در پروژه ی همبرگر ساز – دریافت سفارشات از firebase

در قسمت قبل بخش ثبت سفارشات را با موفقیت تکمیل کردیم و حالا نوبت به دریافت آن ها می رسد. اگر یادتان باشد برنامه ما در منوی navigation خود دارای صفحه ای به نام orders است که سفارشات ثبت شده را نشان می دهد. بهتر است اول از همه به Firebase رفته و قسمت orders را پاک کنید تا سفارشات قبلی را به طول کامل حذف کرده باشیم. سپس یک سفارش جدید ثبت کنید و به صفحه orders بروید.

ما می توانیم به راحتی سفارش ثبت شده را مشاهده کنیم بنابراین هیچ مشکلی نیست اما از redux استفاده نکرده ایم و هدف ما در این پروژه کار با redux است. ما آرایه orders را در order.js (درون پوشه reducer) داریم که مخصوص گرفتن همین سفارشات بود اما هنوز از آن استفاده نکرده ایم:

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

قدم اول برای استفاده از redux در چنین حالتی این است که به actionTypes.js برویم و سه action مختلف دیگر را تعریف کنیم:

export const FETCH_ORDERS_START = 'FETCH_ORDERS_START';
export const FETCH_ORDERS_SUCCESS = 'FETCH_ORDERS_SUCCESS';
export const FETCH_ORDERS_FAIL = 'FETCH_ORDERS_FAIL';

در واقع این سه action دقیقا مانند سه action قبلی بود که برای ثبت سفارشات داشتیم:

export const PURCHASE_BURGER_START = 'PURCHASE_BURGER_START';
export const PURCHASE_BURGER_SUCCESS = 'PURCHASE_BURGER_SUCCESS';
export const PURCHASE_BURGER_FAIL = 'PURCHASE_BURGER_FAIL';

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

export const fetchOrdersSuccess = ( orders ) => {
    return {
        type: actionTypes.FETCH_ORDERS_SUCCESS,
        orders: orders
    };
};

سپس برای عدم موفقیت در دریافت داده ها:

export const fetchOrdersFail = ( error ) => {
    return {
        type: actionTypes.FETCH_ORDERS_FAIL,
        error: error
    };
};

و سپس برای شروع فرآیند دریافت:

export const fetchOrdersStart = () => {
    return {
        type: actionTypes.FETCH_ORDERS_START
    };
};

در نهایت به ثابتی نیاز داریم که کدهای نامتقارن ما را اجرا کند (دقیقا مانند purchaseBurger برای ثبت سفارشات). این کدها با axios نوشته شده اند، بنابراین می توانید همان کدهایی که برای دریافت سفارشات در componentDidMount در فایل Orders.js (در پوشه containers و سپس orders) استفاده کرده بودیم را cut کنید و در این قسمت قرار دهید:

export const fetchOrders = () => {
    return dispatch => {
        axios.get( '/orders.json' )
            .then( res => {
                const fetchedOrders = [];
                for ( let key in res.data ) {
                    fetchedOrders.push( {
                        ...res.data[key],
                        id: key
                    } );
                }
                dispatch(fetchOrdersSuccess(fetchedOrders));
            } )
            .catch( err => {
                dispatch(fetchOrdersFail(err));
            } );
    };
};

البته همانطور که در کد بالا می بینید دیگر SetState را صدا نمی زنیم بلکه از dispatch استفاده می کنیم تا action های خودمان را ارسال کنیم. طبیعتا قدم بعدی دریافت این action های ارسال شده است، بنابراین به order.js در پوشه reducers می رویم تا چند case جدید را اضافه کنیم:

        case actionTypes.FETCH_ORDERS_START:
            return {
                ...state,
                loading: true
            }

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

        case actionTypes.FETCH_ORDERS_SUCCESS:
            return {
                ...state,
                orders: action.orders,
                loading: false
            }

بنابراین orders درون state را برابر با orders دریافت شده از سمت action گذاشته و loading را هم false می کنم چرا که فرآیند دریافت داده ها تمام شده است. سپس حالت شکست و دریافت خطا را مشخص می کنیم:

        case actionTypes.FETCH_ORDERS_FAIL:
            return {
                ...state,
                loading: false
            }

در چنین حالتی نیز loading متوقف می شود چرا که چیزی در حال پردازش نیست. دریافت خطاها نیز با همان کامپوننت HOC خودمان است (withErrorHandler) که در فصل های اول این دوره آموزشی نوشتیم. در نهایت باید fetchOrders را درون index.js قرار بدهیم تا همه جا در دسترس باشد:

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

حالا به فایل Orders.js (در پوشه containers و سپس orders) بروید تا mapDispatchToProps را تعریف کنیم. ابتدا باید فایل index.js را وارد این فایل کنیم:

import * as actions from '../../store/actions/index';

حالا می توانیم mapDispatchToProps را تعریف کرده و action خود را dispatch می کنیم:

const mapDispatchToProps = dispatch => {
    return {
        onFetchOrders: () => dispatch(actions.fetchOrders())
    };
};

حالا در همین فایل به سراغ componentDidMount می رویم و onFetchOrders را صدا می زنیم:

    componentDidMount() {
        this.props.onFetchOrders();
    }

قبلا کدهای axios برای دریافت پست ها در componentDidMount بود که بالاتر به شما گفته بودم آن را cut کنید بنابراین کدهای شما باید مثل من باشد. در مرحله بعد باید مثل همیشه connect را وارد این فایل کنم:

import { connect } from 'react-redux';

و سپس mapDispatchToProps را به connect می دهیم:

export default connect(null, mapDispatchToProps)(withErrorHandler(Orders, axios));

فعلا به جای mapStateToProps از null استفاده کرده ام چرا که هنوز آن را تعریف نکرده ام. حالا که می خواهیم از redux استفاده کنیم نیازی به State داخل این فایل نداریم بنابراین آن را حذف کنید. سپس برای استفاده از redux مثل همیشه mapStateToProps را تعریف می کنیم:

const mapStateToProps = state => {
    return {
        orders: state.order.orders,
        loading: state.order.loading
    };
};

با قسمت state.order به reducer دسترسی پیدا می کنم و سپس با orders. به آرایه orders درون initialState می رسم (فایل order.js درون پوشه reducers). حالا می توانیم connect را درست کنیم:

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

با این کار باید به متد render نگاه کنیم و هر جا this.state بود به this.props تغییر دهیم که فقط یک مورد است:

orders = this.props.orders.map( order => (
// بقیه کدها //

همچنین اگر بخواهید spinner (علامت loading) را نشان بدهیم باید آن را در این فایل داشته باشیم بنابراین دستور import آن را هم اضافه می کنیم:

import Spinner from '../../components/UI/Spinner/Spinner';

سپس در قسمت render با یک شرط if این spinner را نمایش می دهیم:

class Orders extends Component {
    componentDidMount () {
        this.props.onFetchOrders();
    }

    render () {
        let orders = <Spinner />;
        if ( !this.props.loading ) {
            orders = this.props.orders.map( order => (
                <Order
                    key={order.id}
                    ingredients={order.ingredients}
                    price={order.price} />
            ) )
        }
        return (
            <div>
                {orders}
            </div>
        );
    }
}

اگر حالا به مرورگر بروید Spinner را نخواهید دید. چرا؟ به دلیل اینکه هیچ وقت fetchOrdersStart را dispatch نکرده ایم. برای این کار به فایل order.js در پوشه actions بروید و آن را در تابع fetchOrders بنویسید:

export const fetchOrders = () => {
    return dispatch => {
        dispatch(fetchOrdersStart());
        axios.get( '/orders.json' )
            .then( res => {
                const fetchedOrders = [];
                for ( let key in res.data ) {
                    fetchedOrders.push( {
                        ...res.data[key],
                        id: key
                    } );
                }
                dispatch(fetchOrdersSuccess(fetchedOrders));
            } )
            .catch( err => {
                dispatch(fetchOrdersFail(err));
            } );
    };
};

همانطور که می بینید در خط سوم از کد بالا fetchOrdersStart را اضافه کرده ام تا spinner نیز نمایش داده شود.

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

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