در قسمت قبل روی reducer های خود کار کردیم اما کدهای ما تکمیل نشده است و به خطا برمی خوریم. این خطا هنگامی رخ می دهد که مستقیما برنامه را از url مربوط به صفحه checkout باز کنیم. بنابراین قبل از اینکه بخواهیم reducer های خود را ادغام کنیم باید این خطا را برطرف کنیم. اگر به checkout.js درون پوشه ContactData بروید به کد زیر می رسید:
render () { return ( <div> <CheckoutSummary ingredients={this.props.ings} checkoutCancelled={this.checkoutCancelledHandler} checkoutContinued={this.checkoutContinuedHandler} /> <Route path={this.props.match.path + '/contact-data'} component={ContactData} /> </div> ); }
همانطور که می بینید یک پیش نمایش از همبرگر سفارش داده شده را در این قسمت برای کاربر نمایش می دهیم. همچنین قبل از اینکه محتویات را بارگذاری کنیم، ingredients برابر با null است به همین دلیل گردش بین ingredients باعث خطا می شود. چند راه حل برای این مشکل وجود دارد.
راه اول این است که تا زمانی که ingredients بارگذاری نشده اند اصلا CheckoutSummary را نمایش ندهیم. به طور مثال می توانیم به جایش یک spinner نمایش دهیم. البته از آنجایی که در برنامه ما این اتفاق (بارگذاری نشدن ingredients) فقط در هنگام شروع برنامه اتفاق می افتد، می توانیم به سادگی کاربر را redirect کنیم. چرا؟ به دلیل اینکه اگر هیچ محتویاتی وجود ندارد، کاربر دلیلی برای حضور در صفحه checkout نخواهد داشت و باید به صفحه اصلی برنامه برود. این همان راه حل دوم و روش انتخابی ما است.
برای انجام این کار ابتدا کامپوننت redirect را از پکیج react-dom وارد همین فایل (checkout.js) کنید:
import { Route, Redirect } from 'react-router-dom';
و سپس در متد render یک متغیر جدید به نام summary می سازیم:
let summary = <Redirect to="/" />
حالا چک می کنیم که اگر ing (همان ingredients) بارگذاری شده است، پیش نمایش همبرگر نمایش داده شود و در غیر این صورت redirect کردن کاربر را در برنامه مشاهده خواهیم کرد:
render () { let summary = <Redirect to="/" /> if ( this.props.ings ) { summary = ( <div> {purchasedRedirect} <CheckoutSummary ingredients={this.props.ings} checkoutCancelled={this.checkoutCancelledHandler} checkoutContinued={this.checkoutContinuedHandler} /> <Route path={this.props.match.path + '/contact-data'} component={ContactData} /> </div> ); } return summary; }
حالا کدها را ذخیره کرده و به مرورگر می رویم. اگر همبرگر خود را سفارش داده و به صفحه checkout بروید، سپس صفحه را در همان url یک بار refresh کنید (تا state پاک شود) به صفحه اصلی redirect خواهید شد. حالا که خطای ما برطرف شده است باید reducer های خود را ادغام کنیم.
برای ادغام کردن reducer ها باید به فایل index.js اصلی (در پوشه src) بروید و متد combineReducers را وارد کنید:
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
سپس reducer دوم خود را نیز وارد این فایل می کنیم (فایل order.js):
import orderReducer from './store/reducers/order';
بنابراین تمام دستورات import این فایل به شکل زیر در می آید:
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; import './index.css'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; import burgerBuilderReducer from './store/reducers/burgerBuilder'; import orderReducer from './store/reducers/order';
حالا با استفاده از combineReducers می توانیم یک reducer واحد داشته باشیم. همانطور که از پروژه شمارنده به یاد دارید باید یک شیء جاوا اسکریپتی به این متد پاس بدهیم:
const rootReducer = combineReducers({ burgerBuilder: burgerBuilderReducer, order: orderReducer });
در واقع ما ثابتی به نام rootReducer تعریف کرده ایم که دو reducer به نام های burgerBuilder و order را با هم ادغام می کند. حالا به جای reducer قبلی (burgerBuilderReducer)، مقدار rootReducer را به CreateStore پاس می دهیم:
const store = createStore(rootReducer, composeEnhancers( applyMiddleware(thunk) ));
با انجام این کار دوباره برنامه خراب خواهد شد. چرا؟ به دلیل اینکه حالا دو تکه reducer مختلف داریم (شیء جاوا اسکریپتی بالا که به combineReducers پاس دادیم) بنابراین باید این موارد را نیز تصحیح کنیم. ابتدا در فایل BurgerBuilder.js (در پوشه containers) قسمتی به نام mapStateToProps داریم که در آن مشخص نشده است از کدام reducer استفاده کرده ایم بنابراین این مورد را مشخص می کنیم:
const mapStateToProps = state => { return { ings: state.burgerBuilder.ingredients, price: state.burgerBuilder.totalPrice, error: state.burgerBuilder.error }; }
تمام موارد در این فایل از burgerBuilder استفاده می کنند و شما باید این مسئله را در کد خود تصریح کنید تا با خطا روبرو نشوید. همچنین در فایل checkout.js درون پوشه ContactData باید همین کار را انجام دهیم:
const mapStateToProps = state => { return { ings: state.burgerBuilder.ingredients } };
سپس در فایل ContactData نیز همین کار را انجام می دهیم:
const mapStateToProps = state => { return { ings: state.burgerBuilder.ingredients, price: state.burgerBuilder.totalPrice, loading: state.order.loading } };
اگر دقت کنید loading از سمت order می آید بنابراین نباید مقدار burgerBuilder را برایش قرار دهید. برای تست کردن کدها آن ها را ذخیره کرده و به مرورگر بروید. حالا یک همبرگر را سفارش دهید و redux devtools را باز کنید:
همانطور که می بینید من همبرگر خودم را با اطلاعات سمت چپ سفارش داده ام اما در پاسخ دریافتی از firebase در سمت راست صفحه (در redux devtools) مقدار id من صحیح نیست. این نوع id زمانی اتفاق می افتد که id به سمت سرور Firebase ارسال نشده و firebase یک id تصادفی برای آن قرار دهد. همچنین زمانی که کارمان تمام می شود و سفارش ثبت شده است در صفحه checkout مانده ایم در حالی که قرار بود به صفحه اصلی redirect شویم.
حالا به firebase بروید تا ببینیم مقادیر ذخیره شده در آنجا صحیح هستند یا خیر:
همانطور که مشاهده می کنید، مقدار deliveryMethod به درستی ارسال نشده و جای آن خالی است! بنابراین در کل سه مشکل داریم. اولین مشکل در ContactData.js (در پوشه ContactData) است. مشکل اینجاست که اگر هیچ گاه منوی آبشاری deliver method را تغییر ندهیم، مقدار آن خالی خواهد بود. به state این فایل نگاه کنید:
deliveryMethod: { elementType: 'select', elementConfig: { options: [ {value: 'fastest', displayValue: 'Fastest'}, {value: 'cheapest', displayValue: 'Cheapest'} ] }, value: '', validation: {}, valid: true }
رشته خالی value را مشاهده می کنید. باید آن را به شکل زیر تغییر دهید:
deliveryMethod: { elementType: 'select', elementConfig: { options: [ {value: 'fastest', displayValue: 'Fastest'}, {value: 'cheapest', displayValue: 'Cheapest'} ] }, value: 'fastest', validation: {}, valid: true }
مشکل بعدی در order.js در پوشه reducers می باشد. به کد زیر در دستور switch نگاه کنید:
case actionTypes.PURCHASE_BURGER_SUCCESS: const newOrder = { ...action.orderData, id: action.orderId }; return { ...state, loading: false, orders: state.orders.concat(newOrder) }
ما در هنگام ثبت موفقیت آمیز سفارش مقادیری را دریافت می کنیم اما Action.orderId آیدی صحیح نیست! چرا؟ به دلیل اینکه در فایل order.js در پوشه Actions من مقدار id را برابر response.data گذاشته ام:
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 ) ); } ); }; };
در حالی که باید از response.data.name استفاده کنم:
export const purchaseBurger = ( orderData ) => { return dispatch => { dispatch( purchaseBurgerStart() ); axios.post( '/orders.json', orderData ) .then( response => { console.log( response.data ); dispatch( purchaseBurgerSuccess( response.data.name, orderData ) ); } ) .catch( error => { dispatch( purchaseBurgerFail( error ) ); } ); }; };
مشکل سوم مربوط به redirect کردن کاربران است که مبحثی طولانی است و انجام آن را به جلسه بعد موکول می کنیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.