حالا که کارمان با طرح اولیه ی سفارشات تمام شده است می خواهیم در این قسمت سفارشات را از پایگاه داده ی Firebase دریافت کرده و همچنین برخی از کدها را تکمیل کنیم. در همین ابتدا به فایل Order.module.css بروید و خصوصیت width را از 100 روی 80 بیاورید:
.Order { width: 80%; border: 1px solid #eee; box-shadow: 0 2px 3px #ccc; padding: 10px; margin: 10px auto; box-sizing: border-box; }
با انجام این کار ظاهر برنامه بهتر می شود. اگر یادتان باشد در قسمت قبلی تمام سفارشات خود را از پایگاه داده حذف کردیم تا فضای آن خلوت شود اما حالا هیچ همبرگری برای دریافت نداریم. بنابراین به برنامه بروید و دو یا چند سفارش همبرگر را ثبت کنید تا چیزی برای دریافت کردن در پایگاه داده وجود داشته باشد.
حالا به Orders.js می رویم تا این دو سفارش را دریافت کنیم. اولین کاری که می کنیم وارد کردن axios است:
import axios from '../../axios-orders';
سپس درون componentDidMount کدهای دریافت سفارشات را می نویسیم. هیچ راهی وجود ندارد که بدون mount شدن کامپوننت Orders به آن دسترسی داشته باشیم بنابراین componentDidMount انتخاب صحیحی است و نیازی به componentDidUpdate نداریم اما قبل از آن state را برای این کامپوننت می نویسیم:
state = { orders: [], loading: true }
حالا کدهای axios را اضافه می کنیم:
componentDidMount() { axios.get('/orders.json').then(res => { this.setState({ Loading: false }); }).catch(err => { this.setState({ Loading: false }); }) }
در اینجا از متد get استفاده کرده و orders.json را از firebase گرفته ایم. سپس زمانی که پاسخ (res) یا خطایی (err) را دریافت کردیم، loading را غیرفعال می کنیم. قدم بعدی این است که سفارشات را از پایگاه داده دریافت کنیم اما Firebase به جای آرایه یک شیء به ما برمی گرداند. من آن را در console مرورگر صدا می زنم تا پاسخ را ببینید:
componentDidMount() { axios.get('/orders.json').then(res => { console.log(res.data); this.setState({ Loading: false }); }).catch(err => { this.setState({ Loading: false }); }) }
با رفتن به صفحه ی orders مشاهده می کنیم که:
بله این دقیقا همان orders.json ای است که تصویر آن را بالاتر دیدیم. همانطور که می بینید این پاسخ یک شیء جاوا اسکریپتی است اما من می خواهم آن را به یک آرایه تبدیل کنم بنابراین می گویم:
componentDidMount() { axios.get('/orders.json').then(res => { const fetchedOrders = []; for (let key in res.data) { fetchedOrders.push(res.data[key]); } this.setState({ Loading: false }); }).catch(err => { this.setState({ Loading: false }); }) }
در ابتدا یک آرایه ی خالی ایجاد کرده ایم و سپس با یک حلقه ی for تک تک مقادیر را درون آن قرار داده ایم. اگر یادتان نمی آید که این شیء (res.data) چه شکلی دارد به تصویر قبلی مراجعه کنید. البته این کد یک مشکل دارد؛ با انجام این کار تمام key هایمان حذف می شود. Key های ما همان مقادیر طولانی و هش گونه ای است که firebase برای ما تولید کرده است (ر.ک به تصویر بالا) بنابراین به جای آنکه شیء res.data را push کنیم یک شیء جدید ساخته و آن را push می کنیم:
const fetchedOrders = []; for (let key in res.data) { fetchedOrders.push({ ...res.data[key], id: key }); }
با قرار دادن {}
یک شیء جدید ایجاد می شود که دو عضو دارد: اولین آن همان مقادیر و کلیدهای شیء res.data است که با استفاده از اپراتور spread پخش شده اند و دومین آن هم key های ما هستند. حالا می توانیم با تکمیل قسمت setState این کدها را کامل کنیم:
componentDidMount() { axios.get('/orders.json').then(res => { const fetchedOrders = []; for (let key in res.data) { fetchedOrders.push({ ...res.data[key], id: key }); } this.setState({ loading: false, orders: fetchedOrders }); }).catch(err => { this.setState({ Loading: false }); }) }
در کد بالا orders را روی fetchedOrders تنظیم کرده ام.
در قدم بعد باید خطاهای احتمالی را مدیریت کنیم. برای این کار از همان کامپوننت ساخته ی خودمان به نام withErrorHandler کمک می گیریم و آن را وارد می کنیم:
import withErrorHandler from '../../hoc/withErrorHandler/withErrorHandler';
در در نهایت دستور export را درون آن قرار دهیم:
export default withErrorHandler(Orders, axios);
حالا که سفارشات را دریافت کرده ایم باید آن ها را نمایش دهیم. برای این کار ابتدا دو کامپوننت <Order> را از قسمت JSX حذف می کنیم:
render() { return ( <div> </div> ); }
سپس تابع map را روی state خودمان صدا می زنیم تا در سفارشات گردش کرده و آن ها را نمایش دهیم:
return ( <div> {this.state.orders.map(order => ( <Order key={order.id} /> ))} </div> );
حالا اگر به مرورگر برویم به تعداد سفارشاتمان کامپوننت Order خواهیم داشت. در قدم بعد باید از مقادیر دریافت شده در پایگاه داده برای نمایش استفاده کنیم. این اطلاعات را به صورت prop ارسال خواهیم کرد:
return ( <div> {this.state.orders.map(order => ( <Order key={order.id} ingredients={order.ingredients} price={order.price} /> ))} </div> );
حالا که این اطلاعات را به صورت prop پاس داده ایم باید آن ها را نمایش بدهیم. بنابراین به فایل Order.js بروید. برای نمایش قیمت کارمان ساده است:
<p>Price: <strong>USD {props.price}</strong></p>
اگر به مرورگر برویم می بینیم که قیمت با تعداد زیادی اعشار ذخیره شده است که مربوط به جاوا اسکریپت و firebase است. برای تصحیح آن باید مثل همیشه از toFixed استفاده کنیم اما toFixed فقط روی اعداد کار می کند و قیمت برگشت داده شده از firebase رشته است. ما می توانیم با صدا زدن Number.parseFloat این مشکل را حل کنیم:
<p>Price: <strong>USD {Number.parseFloat(props.price).toFixed(2)}</strong></p>
حالا که مشکل قیمت برطرف شده است باید محتویات همبرگر را به شکل صحیح نمایش دهیم. مسئله ی نمایش محتویات کمی پیچیده تر است چرا که محتویات به شکل یک شیء جاوا اسکریپتی به ما برگردانده می شود اما ما باید یک آرایه داشته باشیم تا بتوانیم درون آن گردش کنیم. برای حل این مشکل ابتدا شکل کامپوننت order را تغییر می دهیم. در حال حاضر این کامپوننت به شکل زیر است:
const order = (props) => ( <div className={classes.Order}> <p>Ingredients: Salad (1)</p> <p>Price: <strong>USD {Number.parseFloat(props.price).toFixed(2)}</strong></p> </div> );
در خط اول از کد بالا می بینید که بدنه ی تمام کامپوننت درون پرانتز قرار گرفته است. قبلا گفته بودم که اگر هیچ کدی به غیر از JSX نداشته باشیم می توانیم JSX را درون پرانتز گذاشته و مانند بالا آن را return کنیم اما حالا که می خواهیم کدهای محاسباتی جاوا اسکریپت را نیز در آن بنویسیم باید آن را به یک تابع ساده تغییر دهیم:
const order = (props) => { return ( <div className={classes.Order}> <p>Ingredients: Salad (1)</p> <p>Price: <strong>USD {Number.parseFloat(props.price).toFixed(2)}</strong></p> </div> ); }
در این مرحله باید یک آرایه از شیء ingredients بسازیم. قبلا منطق تبدیل شیء به آرایه را در burger.js به شما نشان داده بودم (اگر یادتان رفته به قسمت transformedIngredients آن سر بزنید) اما در این فایل می خواهم یک روش جدید را به شما آموزش بدهم:
const order = ( props ) => { const ingredients = []; for ( let ingredientName in props.ingredients ) { ingredients.push( { name: ingredientName, amount: props.ingredients[ingredientName] } ); } // بقیه ی کدها //
ابتدا یک آرایه ی خالی ایجاد کرده ام، سپس با یک حلقه ی for درون شیء ingredients گردش کرده ام و یک شیء جدید را به آرایه ی خالی ingredients ارسال کرده ام که شامل name (یعنی نام محتویات، مثلا سالاد، گوشت و...) و amount (تعداد گوشت یا تعداد سالاد و ..) می باشد. حالا یک ثابت دیگر ایجاد می کنیم و محتویات را درون آن نمایش می دهیم:
const ingredientOutput = ingredients.map(ig => { return <span key={ig.name}>{ig.name} ({ig.amount})</span> })
من برای key از نام مواد همبرگر استفاده کردم ام چون یکتا هستند. سپس در قسمت JSX از آن استفاده می کنیم:
return ( <div className={classes.Order}> <p>Ingredients: {ingredientOutput}</p> <p>Price: <strong>USD {Number.parseFloat(props.price).toFixed(2)}</strong></p> </div> );
اگر الان به مرورگر بروید با صحنه ی جالبی روبرو نخواهید شد چرا که هیچ استایلی برای <span> هایمان وجود ندارد. من برای استایل دهی از همان ثابت بالا (ingredientOutput) استفاده می کنم و استایل های inline را به آن اضافه می کنم:
const ingredientOutput = ingredients.map(ig => { return <span style={{ textTransform: 'capitalize', display: 'inline-block', margin: '0 8px', border: '1px solid #ccc', padding: '5px' }} key={ig.name}>{ig.name} ({ig.amount})</span>; });
حالا شکل آن ها بهتر می شود. امیدوارم تمام فرآیند را به طور کامل درک کرده باشید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.