در جلسه ی قبل یک error handler سراسری برای برنامه ی خود تعریف کردیم و می توانیم سفارشات را در پایگاه داده مان ثبت کنیم اما هنوز نمی توانیم سفارشات را دریافت کنیم. اگر یادتان باشد محتویات همبرگر (ingredients) را بدین شکل تعریف کرده بودیم:
state = { ingredients: { salad: 0, bacon: 0, cheese: 0, meat: 0 }, totalPrice: 4, purchasable: false, purchasing: false, loading: false }
حالا می خواهیم این محتویات را از سمت سرور دریافت کنیم. برای انجام این کار ابتدا به Firebase بروید. Node ای به نام orders را مشاهده می کنید که از جلسه ی قبل باقی مانده است. ما با این node کاری نداریم بلکه باید یک node جدید ایجاد کنیم (شما می توانید با کلیک روی علامت + این کار را انجام دهید) و نام آن را ingredients قرار دهیم. سپس دقیقا مانند key/value های بالا برای ingredients، محتویات مختلف را با آن اضافه می کنیم تا به شکل زیر در بیاید:
حالا اگر روی ingredients کلیک کنیم به آدرس آن می رویم:
این آدرسی که می بینید (https://react-my-burger-4229e.firebaseio.com/ingredients) همان آدرسی است که باید به برنامه مان بدهیم. حالا قصد داریم به جای تعریف مقادیر اولیه در state، آن ها را مستقیما از پایگاه داده دریافت کنیم بنابراین به فایل BurgerBuilder.js بروید تا از componentDidMount استفاده کنیم. قبل از آن باید state قبلی برنامه را به شکل زیر تغییر دهیم:
state = { ingredients: null, totalPrice: 4, purchasable: false, purchasing: false, loading: false }
یعنی مقدار ingredient را که قبلا یک شیء جاوا اسکریپتی بود، برابر با null قرار دادم تا فقط داده ها را از سمت پایگاه داده بگیریم. همچنین برای componentDidMount می گوییم:
componentDidMount() { axios.get('https://react-my-burger-4229e.firebaseio.com/ingredients.json') .then(response => { this.setState({ ingredients: response.data }); }); }
همانطور که می بینید این یک درخواست ساده ی get است که با متد then در ادامه ی آن state را تنظیم کرده ایم. کلید data در شیء response از طرف firebase ارسال می شود و حاوی همان شیء ingredients خواهد بود. اگر الان به مرورگر بروید با خطای زیر مواجه می شوید:
TypeError: Cannot convert undefined or null to object
آیا می دانید چرا؟ اگر یادتان باشد ما در کدمان مواردی را قرار دادیم که بر اساس state کار می کنند. به طور مثال:
return ( <Aux> <Modal show={this.state.purchasing} modalClosed={this.purchaseCancelHandler}> {orderSummary} </Modal> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} disabled={disabledInfo} purchasable={this.state.purchasable} price={this.state.totalPrice} ordered={this.purchaseHandler} /> </Aux> );
در کد بالا عناصری مانند Burger وجود دارند که از state استفاده می کنند (this.state.ingredients) و به محض شروع بارگذاری برنامه نیز اجرا می شوند. به همین علت زمان کافی برای ارسال درخواست به Firebase، دریافت پاسخ و سپس نمایش آن وجود ندارد که باعث می شود نتیجه ی this.state.ingredients برابر با null شود.
من برای حل این مشکل می خواهم تا زمانی که پاسخ از Firebase دریافت نشده است صبر کنم و به کاربر یک spinner نمایش دهم. برای این کار بیرون از متد return (قسمت JSX) یک متغیر جدید ایجاد می کنم (به نام burger) و عناصر Burger و BuildControls را از قسمت JSX برداشته و درون آن قرار می دهم:
if (this.state.loading) { orderSummary = <Spinner />; } let burger = ( <Aux> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} disabled={disabledInfo} purchasable={this.state.purchasable} price={this.state.totalPrice} ordered={this.purchaseHandler} /> </Aux> );
از آنجایی که react نیاز به عنصر ریشه ای دارد از یک Aux هم استفاده کرده ام تا از متغیرم ایراد گرفته نشود. ما می خواهیم متغیر burger زمانی مساوی محتوای اصلی (کد بالا) باشد که پاسخ از سمت firebase ارسال شده باشد بنابراین نیاز به ایجاد یک تغییر کوچک داریم؛ من متغیر burger را ابتدا روی یک spinner تعریف می کنم و سپس با یک شرط if محتوای اصلی را به آن می دهم:
let burger = <Spinner />; if (this.state.ingredients) { burger = ( <Aux> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} disabled={disabledInfo} purchasable={this.state.purchasable} price={this.state.totalPrice} ordered={this.purchaseHandler} /> </Aux> ); }
در شرط if گفته ایم اگر مقدار state.ingredients برابر با true باشد (یعنی وجود داشته باشد و null نباشد) آنگاه همبرگر ما باید به شکل عادی نمایش داده شود. حالا می توانیم به قسمت JSX برویم (درون return) و متغیر burger را در آن بگذاریم:
return ( <Aux> <Modal show={this.state.purchasing} modalClosed={this.purchaseCancelHandler}> {orderSummary} </Modal> {burger} </Aux> );
اما هنوز هم مشکلی داریم. به کد زیر نگاه کنید:
let orderSummary = <OrderSummary ingredients={this.state.ingredients} price={this.state.totalPrice} purchaseCancelled={this.purchaseCancelHandler} purchaseContinued={this.purchaseContinueHandler} />;
بله OrderSummary نیز از state.ingredients استفاده می کند بنابراین با خطا مواجه می شویم. برای حل این مشکل ابتدا OrderSummary را روی null میگذاریم و سپس با یک شرط if ساده محتوای اصلی را به آن برمی گردانیم. یعنی ابتدا orderSummary را روی null گذاشته:
let orderSummary = null;
سپس مقدار واقعی آن را در همان شرط قبلی برای burger قرار می دهیم:
if (this.state.ingredients) { burger = ( <Aux> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} disabled={disabledInfo} purchasable={this.state.purchasable} price={this.state.totalPrice} ordered={this.purchaseHandler} /> </Aux> ); orderSummary = <OrderSummary ingredients={this.state.ingredients} price={this.state.totalPrice} purchaseCancelled={this.purchaseCancelHandler} purchaseContinued={this.purchaseContinueHandler} />; }
چرا این کار را کردیم؟ به دلیل اینکه یک بار this.state.ingredients را چک کرده ایم و نیازی ندارد که یک شرط را دوباره نویسی کنیم. جدا از شلوغ شدن بیهوده ی کدها، سرعت برنامه را هم کاهش می دهیم. در نهایت برای تکمیل شدن کد باید شرط loading را به بعد از کد بالا منتقل کنیم:
let orderSummary = null; let burger = <Spinner />; if (this.state.ingredients) { burger = ( <Aux> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} disabled={disabledInfo} purchasable={this.state.purchasable} price={this.state.totalPrice} ordered={this.purchaseHandler} /> </Aux> ); orderSummary = <OrderSummary ingredients={this.state.ingredients} price={this.state.totalPrice} purchaseCancelled={this.purchaseCancelHandler} purchaseContinued={this.purchaseContinueHandler} />; } if (this.state.loading) { orderSummary = <Spinner />; }
منظور من شرط آخر است (this.state.loading). این شرط باید بعد از شرط اول اضافه شود تا علمیات loading معنا پیدا کند.
حالا اگر به مرورگر مراجعه کنیم، کدهای خود را صحیح و سالم می بینیم. در واقع در چند ثانیه ی اول یک spinner را مشاهده می کنیم که همان فرآیند بارگذاری داده ها از Firebase است. نکته ی جالب اینجاست که اگر به firebase بروید و مقدار محتوای اولیه را تغییر دهید (مثلا مقدار meat را روی 1 قرار دهید) همیشه با همان مقدار اولیه شروع خواهید کرد، حتی اگر صفحه را refresh کنید.
در جلسه ی بعد یک مشکل واضح از برنامه مان را بررسی می کنیم!
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.