概要
BtoC向けEcshopの商品一覧から商品を選択して買いものカゴに加える動きの実装を行います。
記事の内容としましては、フロントエンドの実装方法が中心になります。
2つの記事を元に実装していきます。
繋げるだけだから簡単(なはず)
フロントNext.js バックエンドSpringBootRESTAPIで買い物カゴ実装
バック:SpringBoot フロント:React ページネーション付き商品一覧機能を実装する
ユースケース
杏林堂ネットスーパーの動きを参考にします。
以下gifアニメは杏林堂ネットスーパーを使っている様子
1.商品一覧機能で表示されている商品を購入ボタンを押す
2.ボタンを押すと、買い物カゴに商品が追加される。
開発環境
OS:windows10
バックエンド側:
IDE:IntelliJ Community
spring-boot-starter-parent 2.75
java : 11
データベース
mysql:8.0.29
クライアントソフト: MySQL Workbench
フロントエンド:
IDE:VScode
├── @types/node@18.11.15
├── @types/react-dom@18.0.9
├── @types/react@18.0.26
├── axios@1.2.1
├── eslint-config-next@13.0.6
├── eslint@8.29.0
├── next-auth@4.18.7
├── next@13.0.6
├── react-bootstrap@2.7.0
├── react-dom@18.2.0
├── react@18.2.0
├── styled-components@5.3.6
├── styled-jsx@5.1.1
└── typescript@4.9.4
実装
GetMethodのAPIを実行したときのレスポンスの例
productResponse.json
{
"content": [
{
"id": 0,
"name": "国産 豚肩ロース 100g",
"description": "赤身と脂身が良いバランスで、とても食べやすい部位。",
"inStock": true,
"categoryId": 1,
"price": 250.0,
"taxRate": 0.08,
"discountPercent": 0.0,
"image": "1"
},
{
"id": 1,
"name": "アメリカ産 豚切りおとし 230g",
"description": "",
"inStock": true,
"categoryId": 1,
"price": 455.0,
"taxRate": 0.08,
"discountPercent": 0.0,
"image": "1"
},
{
"id": 2,
"name": "国産 豚肉かたロース 150g",
"description": "des",
"inStock": true,
"categoryId": 1,
"price": 387.0,
"taxRate": 0.08,
"discountPercent": 0.0,
"image": "2"
}
],
"pageNo": 0,
"pageSize": 3,
"totalElements": 55,
"totalPages": 19,
"last": false,
"categoryId": 1,
"categoryName": "食肉"
}
cartItemResponse.json
{
"cartItemDtos": [
{
"id": 45,
"customerId": 25,
"productId": 34,
"quantity": 1,
"productName": "和牛かたスライス 230g",
"priceWithoutTax": 2024.0,
"priceWithTax": 2185.9202
},
{
"id": 46,
"customerId": 25,
"productId": 32,
"quantity": 3,
"productName": "アメリカ産 牛タン 70g",
"priceWithoutTax": 558.0,
"priceWithTax": 602.64
},
{
"id": 47,
"customerId": 25,
"productId": 6,
"quantity": 3,
"productName": "国産 豚肉スペアリブ 300g",
"priceWithoutTax": 534.0,
"priceWithTax": 576.72003
}
],
"productCost": 5300.0,
"shippingCost": 0.0,
"subTotal": 5300.0,
"tax": 424.0,
"total": 5724.0
}
実装のために作成したファイル一覧
Proudct
ファイル名 | 説明 |
---|---|
paginationV2.tsx | ページネーションのコンポーネント |
productContentV2.tsx | 商品の情報を表示するコンポーネント |
productService.ts | axiosで作ったapi処理が入っているファイル productドメインを対象 |
productResponse.d.ts | 商品情報を取得するAPIを実行したときのレスポンスを定義 |
productView.tsx | 商品一覧機能の完成形、productContentとpaginationを組み合わせたコンポーネント |
product.d.ts | 商品情報単体の型定義ファイル、productResponse上で使う。 |
CartItem
ファイル名 | 説明 |
---|---|
cartItemServiceV2.ts | axiosで作ったapi処理をが入っているファイル cartItemドメインを対象 |
cartMainItemV3.tsx | 金額側のコンポーネントファイル 一つだけ生成 |
cartSubItemV3.tsx | 商品のコンポーネントファイル 商品の数だけコンポーネントを生成する |
cartItemDto.d.ts | 1つの商品の型定義ファイル REST-APIの出力結果に合わせてる |
cartItemResponse.d.ts | Apiのレスポンスに関する型定義ファイル 商品の配列 金額情報を定義している |
cartItemView.tsx | 買い物カゴの完成形 cartMainとcartSubItemを組み合わせたコンポーネント |
その他
ファイル名 | 説明 |
---|---|
pages/develop/ProductAndCartItem/index.tsx
|
ページのURL state、コンテキスト、ProductViewとCartItemViewの位置の定義を行っているファイル |
実装の方針
データ取得をするApiのpttpレスポンスをStateにして、Contextで子コンポーネントに渡しています。
httpレスポンスが変化するたびにレンダリングが行われるので、適切なタイミングで
現時点ではNext.jsの機能は活用できていないです。ページ機能ぐらいだけです。
実質的にTypeScript+Reactで実装しています。(よくない)
コード(長いので先に「作成したファイル一覧」を御覧ください)
paginationV2
import { setHttpClientAndAgentOptions } from "next/dist/server/config";
import { useState, useEffect, useCallback, EventHandler, SetStateAction, Dispatch, useContext } from "react";
import { MouseEventHandler } from "react";
import { mainContext } from './index';
/**
* @remarks ページネーション コンポーネント
* 親コンポーネントに現在のページ数を渡します。
* RestApiを呼ぶ関数は親コンポーネント側で行います。
*
* @param totalPage: 総ページ数:固定値として使われる想定
* @param pageNo:現在のページ番号を取得する useState[pageNo, setPageNo]の1つ目の引数
* @param setPageNo:現在のページ番号を更新する useState[page,setPage]の2つ目の引数
*/
export default function PaginationV2(props:{totalPage:number ,pageNo:number, setPageNo:Dispatch<SetStateAction<number>>}) {
let currentPageNo = 1;
const totalPage: number = 10;
let paginationContent: JSX.Element[] = [];
// 前へボタンを押したときの処理
const backHandler = () => {
props.setPageNo( props.pageNo - 1)
};
// 次へボタンを押したときの処理
const nextHandler = () => {
props.setPageNo(props.pageNo + 1)
console.log(props.pageNo);
};
// ページ番号を押したときの処理
const selectNumberHandler = (pg: number) => {
props.setPageNo(pg)
};
return (
<>
<div className="Layout">
{/* 前のページに戻る-ページ数が最初の場合は表示されない */}
<div className="Child">
{props.pageNo != 1 ?
<button className="Font" onClick={backHandler}>前へ</button>
:<div className="Font">    </div> // 「前へ」ボタンが非表示でもレイアウトを維持するための処置。(何もしないと崩れる)
}
</div>
{/* 即時関数を使って1からtotalNumberの数字を作成する。クリックすると押した番号のデータを取得するrestapiを呼ぶ関数を実行する */}
{(() => {
for (let i = 0; i < props.totalPage; i++) {
paginationContent.push(
<>
<button
className= { props.pageNo== i+1 ? 'SelectedChild' :'Child'}
onClick={()=>selectNumberHandler(i+1)}
>
{i + 1}
</button>
</>
);
}
return <div>{paginationContent}</div>;
})()}
{/* 次のページへ進む-ページ数が最後の場合は表示されない */}
<div className="Child">
{props.pageNo != props.totalPage ?
<button className="Font" onClick={nextHandler}>次へ</button>
:<div className="Font">    </div> // レイアウトを維持するために空欄を入れる
}
</div>
 
</div>
<style jsx>{`
// 大枠 要素を横並びにする
.Layout {
display: flex;
flex-wrap: wrap;
align-items: center;
}
// 子の幅 間隔を決定する
.Child {
flex-basis: auto;
align-items: center;
background-color: white;
margin: 3px;
border: none;
}
// 現在のページを明示するために背景色を変える 透明な青色とする
.SelectedChild{
flex-basis: auto;
align-items: center;
background-color: #1c5bcf85;
margin: 5px;
border: none;
}
// 文字のフォントサイズ 背景色設定
.Font {
align-items: center;
background-color: white;
text-align: center;
}
// マウスオーバー時の背景色は透明な灰色
button:hover {
background-color: #1c5bcf85;
}
// 子要素のボタン感を無くすために 色と境界線を無くす
button {
background-color: white;
border: none;
}
`}</style>
</>
);
}
productContent
import Image from 'next/image';
import { useContext, useState,useEffect } from 'react';
import { mainContext } from '.';
import * as CartItemService2 from "../../../service/cartItemServiceV2";
import { CartItemResponse } from '../../../types/cartItem/cartItemResponse';
interface Props{
id:number;
productName:string;
priceWithoutTax:number;
priceIncludingTax:string;
imageURL:string;
}
/**
*
* @param props 商品情報
* @returns 商品情報を表示するコンポーネント
*/
export default function ProductContentV2(props : Props){
// 個数のプルダウンメニューの値
const[tempNum,setTempNum] = useState<number>(1);
// 買い物カゴ情報をAPIから取得した後、値をを更新するため利用
const {setCartItemsResponse}: any = useContext(mainContext);
// 商品情報の取得に利用
const {productResponse}:any = useContext(mainContext)
// 商品情報を再取得したときに個数の値を1にする
useEffect(()=>{
setTempNum(1)
},[productResponse])
// 購入ボタンを押したとき、買い物かごに商品を追加して、買い物かご情報を取得する。
const addProductHandler = async (event: React.MouseEvent<HTMLButtonElement>) => {
await addCartItem();
await getCartItem();
}
// 個数のプルダウンリストを変更したときに実行される。”一時的”に値を収納する
const onChangeHandler = async(e: React.ChangeEvent<HTMLSelectElement>) => {
await setTempNum(Number(e.target.value))
}
// 新しい商品を買い物カゴに追加する
const addCartItem = async () => {
const response = await CartItemService2.addProductCart2(
props.id,tempNum,
localStorage.getItem("accessToken") as unknown as string
);
};
// ユーザーの買い物カゴ情報を取得する
const getCartItem = async () => {
let response: CartItemResponse = await CartItemService2.getCartItems2(
localStorage.getItem("accessToken") as unknown as string
);
setCartItemsResponse(response);
};
return(
<>
<div className = 'ContentPadding ContentLayout'>
<Image src={props.imageURL}
width={160}
height={150}
alt='logo' />
<div>{props.productName}</div>
<div className='PriceWithoutTaxAndQuantityLayout'>
<div>{`税抜` + props.priceWithoutTax +`円`}</div>
<div>数量</div>
<div>
<select value={tempNum} onChange={(e: React.FormEvent<HTMLSelectElement>)=>onChangeHandler(e)}>
<option value={tempNum}>{tempNum}</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
</div>
<div className='PriceIncludingTaxAndBuyButtonLayout'>
<div>{`税込` + props.priceIncludingTax +`円`}</div>
<button onClick={addProductHandler} >購入</button>
</div>
</div>
<style jsx>{`
// 大枠の空白部分を担当
.ContentPadding{
padding: 20px;
border:1px solid black;
}
// 大枠のレイアウト(縦並び)
.ContentLayout{
position:relative;
flex:1;
width:200px;
height:350px;
}
// 税抜き価格と数量のレイアウト(横並び-等間隔)
.PriceWithoutTaxAndQuantityLayout{
display:flex;
justify-content:space-between;
}
// 税込み価格と購入ボタンのレイアウト(横並び-等間隔)
.PriceIncludingTaxAndBuyButtonLayout{
display:flex;
justify-content:space-between;
}
`}</style>
</>
)
}
productService
import axios, { AxiosResponse } from 'axios';
import { Product } from '../types/product/product';
import { ProductResponse } from '../types/product/productResponse';
export const getProductsByCategoryId2 = async ( pageNo : number,pageSize:number, categoryId:number) =>{
return await axios.get(`http://127.0.0.1:5000/api/products?`
+`pageNo=`+ pageNo
+`&pageSize=`+ pageSize
+`&category=`+ categoryId,
)
.then((response)=>{
return response.data as AxiosResponse<ProductResponse>;
})
.catch((error)=>{
return error;
});
};
productResponse
export type ProductResponse = {
content : Product[]
pageNo : number
pageSize : number
totalElements : number
totalPages : number
isLast : boolean
categoryId : number
categoryName : string
}
productView
import { ProductResponse } from '../../../types/product/productResponse';
import { createContext, SetStateAction, useContext, useEffect, useState } from 'react';
import { mainContext } from '.';
import * as productServiceV2 from '../../../service/productServiceV2';
import { Product } from '../../../types/product/product';
import ProductContentV2 from './productContentV2';
import PaginationV2 from './paginationV2';
export default function ProductView(){
// 商品情報のhttpレスポンス
const {productResponse,setProductResponse}:any = useContext(mainContext)
// ページ番号
const [pageNo,setPageNo] = useState<number>(1)
// 1ページあたりの商品数
const pageSize : number = 10;
// 検証のためにカテゴリーIDを1に固定
const categoryId : number = 1;
// pageNoが変わるとgetProductByCategoryが実行される
useEffect(() => {
getProductByCategory()
},[pageNo])
// ページネーション化されたカテゴリー毎の商品情報たちを取得する。
const getProductByCategory = async () => {
const response : ProductResponse= await productServiceV2.getProductsByCategoryId2(pageNo - 1,pageSize,categoryId)
setProductResponse(response)
}
return(
<>
<div className="Layout">
{
productResponse?.content.map((product: Product,index:number)=>{
return(
<div className="Child" key={index}>
<ProductContentV2 productName={productResponse.content[index].name} priceWithoutTax={productResponse.content[index].price} priceIncludingTax={(productResponse.content[index].price * (1 + productResponse.content[index].taxRate)).toFixed() as string} imageURL={'/sampleProduct1.JPG'} id={productResponse.content[index].id}/>
</div>
)
})
}
</div>
<div className="PaginationPosition">
<PaginationV2 totalPage={productResponse?.totalPages as number} pageNo={pageNo} setPageNo={setPageNo}/>
</div>
<style jsx>{`
// 大枠
.Layout{
display:flex;
flex-wrap:wrap;
background-color:white;
}
// 子の幅を指定 これしないとレイアウトが崩れる
.Child{
flex-basis:200px;
//align-self:stretch;
}
.PaginationPosition{
margin:auto;
width:50%
}
`}</style>
</>
)
}
product.d
/**
* @remarks 商品情報です ページネーション化されたprooduct.d.tsで使われます。
*
* @author RYA234
*
*/
export type Product = {
id:number
name:string
description:string
inStock:boolean
categoryId:number
price:number
taxRate:number
discountPercent:number
image:string
}
cartItemService
import axios, { AxiosResponse } from 'axios';
import { response } from 'msw';
/**
*
* @remarks SpringBootで作ったAPIを呼ぶ関数
*
*
* @param jwtAccessKey
* @returns
*/
export function addProductCart2(productId:number,quantity:number, jwtAccessKey:string){
return axios.post(`http://127.0.0.1:5000/api/cart/add`
+ `?productId=` +productId
+ `&quantity=` +quantity
,{},
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`,
'Request-Method' : 'POST',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'http://127.0.0.1:5000/*',
'Access-Control-Allow-Headers': 'accept, accept-language, content-language, content-type',
'Access-Control-Allow-Credentials': 'true'
}}
).then((response)=>{
console.log("cartItems is added");
})
.catch((error) => {
console.log("cartItems are not set");
});
}
export const getCartItems2 = async (jwtAccessKey : string) => {
return await axios.get(`http://127.0.0.1:5000/api/cart/all`,
{
headers: {
'Authorization': `Bearer ${jwtAccessKey}`
}
}
)
.then((response) => {
{
const cartItems: CartItem[] = response.data;
console.log(cartItems);
console.log("service is ended");
return response.data as AxiosResponse<CartItem[]>;
}
})
.catch((error) => {
console.log("cartItems are not set");
return error;
});
}
export const updateQuantity2 = async (productId:number,quantity:number,jwtAccessKey:string) =>{
return await axios.put(`http://127.0.0.1:5000/api/cart/update`
+ `?productId=` +productId
+ `&quantity=` +quantity,
{},
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`,
'Request-Method' : 'PUT',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'http://127.0.0.1:5000/*',
'Access-Control-Allow-Headers': 'accept, accept-language, content-language, content-type',
'Access-Control-Allow-Credentials': 'true'
}}
)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log("update is failed");
});
}
export const removeProduct2 = async (productId:number, jwtAccessKey:string) =>{
return await axios.delete(`http://127.0.0.1:5000/api/cart/remove`
+`?productId=`+ productId,
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`
}}
)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error);
});
;
}
cartMainItem
import { useContext, useState } from "react";
import {mainContext } from './index';
// ショッピングカートの金額情報を表示するコンポーネント
export default function CartMainItemV3(this :any) {
const {cartItemsResponse} : any= useContext(mainContext)
console.log("cartItemsResponse is" + cartItemsResponse)
return (
<div className="Layout" key={cartItemsResponse}>
<div className="Title">買い物カゴの中身</div>
<button className="PurchaseButton">決済へ</button>
<div className="MoneyInfomation">
<div>合計額:</div>
{/* cartItemsResponse.totalだとエラーになる「TypeError: Cannot read properties of undefined」 */}
<div>{cartItemsResponse?.total.toFixed()}円</div>
</div>
<div className="MoneyInfomation">
<div>商品小計:</div>
<div>{cartItemsResponse?.productCost}円</div>
</div>
<div className="MoneyInfomation">
<div>配送料:</div>
<div>{cartItemsResponse?.shippingCost}円</div>
</div>
<div className="MoneyInfomation">
<div>消費税:</div>
<div>{cartItemsResponse?.tax.toFixed()}円</div>
</div>
<style jsx>{`
footer{
}
.Layout{
display: flex;
flex-direction: column;
width:200px;
aligh-items: center;
border: 1px solid black;
}
.Title{
align-self: center;
}
.PurchaseButton{
width:100px;
align-self: center;
}
.MoneyInfomation{
display: flex;
flex-direction: row;
justify-content:space-around;
}
`}</style>
</div>
)
}
cartSubItemV
import React, { useContext, useState } from "react";
import {mainContext } from "./index";
import * as cartItemServiceV2 from "../../../service/cartItemServiceV2";
import { CartItemResponse } from "../../../types/cartItem/cartItemResponse";
// ショッピングカートの商品部分のコンポーネント
export default function CartSubItemV3(props:{index:number}) {
const { cartItemsResponse, setCartItemsResponse}: any = useContext(mainContext);
// 数量を変えたときに一時的に収納する値 修正ボタンを押したときこの値に入っている値を処理をする
let [tempNum, setTempNum] = useState<number>(cartItemsResponse?.cartItemDtos[props.index]?.quantity);
// 「削除」ボタンを押した時の処理 コンポーネントを削除して情報の更新
const onDeleteButtonHandler = async (event: React.MouseEvent<HTMLButtonElement>) => {
await removeCartItem();
await getCartItem();
};
// 「修正」ボタン押した時の処理-数量を変更して描画更新
const onQuantityChangeButtonHandler = async (
event: React.MouseEvent<HTMLButtonElement>
) => {
await updateCartItem();
await getCartItem();
};
//プルダウンリストで数量変更を変更したときに実行。”一時的”に値を保持する。
const onChangeHandler = (e: React.FormEvent<HTMLSelectElement>) => {
setTempNum(e.currentTarget.value as unknown as number);
console.log(
`商品名:${cartItemsResponse.cartItemDtos[props.index].productName}のセレクトボックス表示が${tempNum} に変更されました。)`
);
};
// データベースからユーザーの買い物カゴの商品情報を取得する関数
const getCartItem = async () => {
let response: CartItemResponse = await cartItemServiceV2.getCartItems2(
localStorage.getItem("accessToken") as unknown as string
);
setCartItemsResponse(response);
};
//買い物カゴの 商品を一つ削除
const removeCartItem = async () => {
await cartItemServiceV2.removeProduct(
cartItemsResponse.cartItemDtos[props.index].productId,
localStorage.getItem("accessToken") as string
);
console.log(removeCartItem);
};
// 買い物カゴに入った数量を変更
const updateCartItem = async () => {
await cartItemServiceV2.updateQuantity2(
cartItemsResponse.cartItemDtos[props.index].productId,
tempNum,
localStorage.getItem("accessToken") as string
);
};
return (
<div className="Layout">
<div>{cartItemsResponse?.cartItemDtos[props.index].productName}</div>
<div>税抜{cartItemsResponse?.cartItemDtos[props.index].priceWithoutTax}円</div>
<div className="IncludingTax">
税込{cartItemsResponse?.cartItemDtos[props.index].priceWithTax.toFixed()}円
</div>
<div className="SubLayout">
<select
value={tempNum}
onChange={(e: React.FormEvent<HTMLSelectElement>) =>
onChangeHandler(e)
}
>
<option value={tempNum}>{tempNum}</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<button
className="ModifyButton"
onClick={onQuantityChangeButtonHandler}
>
修正
</button>
<button className="DeleteButton" onClick={onDeleteButtonHandler}>
削除
</button>
</div>
<style jsx>{`
.Layout {
display: flex;
flex-direction: column;
width: 200px;
aligh-items: center;
border: 1px solid black;
}
.SubLayout {
display: flex;
flex-direction: row;
column-gap: 10px;
}
.IncludingTax {
color: red;
}
.ModifyButton {
background-color: green;
color: white;
}
.DeleteButton {
background-color: red;
color: white;
}
`}</style>
</div>
);
}
cartItemDto
export type CartItemDto={
id:number
customerId:number
productId:number
quantity:number
productName:string
priceWithoutTax:number
priceWithTax:number
}
cartItemReponse
// CartItemを取得した時のレスポンスの型定義
export type CartItemResponse = {
// 各々商品の情報
cartItemDtos : CartItemDto[]
// 商品の合計額(税抜き)
productCost:number
// 配送料(4000円だと無料)
shippingCost:number
// ???
subTotal:number
// 消費税の合計額
tax:number
// お客様が支払う金額
total:number
}
cartItemView
import { CartItemResponse } from "../../../types/cartItem/cartItemResponse";
import { useContext } from 'react';
import { mainContext } from './index';
import CartMainItemV3 from "./cartMainItemV3";
import CartSubItemV3 from './cartSubItemV3';
import { CartItemDto } from '../../../types/cartItem/cartItemDto';
// 買い物カゴの金額情報と買い物カゴに入れた商品情報たちを束ねてるコンポーネント
export default function CartItemView(){
// carItemのResponse Contextで子コンポーネントに渡す
const {cartItemsResponse} : any =useContext(mainContext);
// 新しい商品を買い物カゴに追加する
return (
<div>
{/* MainItemsの実装 金額関係 */}
<CartMainItemV3 />
{/* SubItemsの実装 買い物カゴにいれた商品 */}
{cartItemsResponse?.cartItemDtos.map((cartItemDtos:CartItemDto, index:number) => {
return (
<>
<CartSubItemV3 index={index} />
</>
);
})}
</div>
);
}
pages/develop/ProductAndCartItem/index
import { createContext, useState,useEffect } from "react";
import { CartItemResponse } from "../../../types/cartItem/cartItemResponse";
import { Customer } from "../../../types/customer";
import { ProductResponse } from "../../../types/product/productResponse";
import * as CustomerService from "../../../service/customerService";
import * as CartItemService2 from "../../../service/cartItemServiceV2";
import ProductView from "./productView";
import CartItemView from "./cartItemView";
export const mainContext = createContext(0);
// 商品一覧機能と買い物カゴ機能をつなげる方法を確認するページ
// http://127.0.0.1:3000/develop/cartItemAndProduct
export default function Index() {
// 商品情報のhttpレスポンス
const [productResponse, setProductResponse] = useState<ProductResponse>();
// 買い物カゴ情報のhttpレスポンス
const [cartItemsResponse, setCartItemsResponse] = useState<CartItemResponse>();
// 初回時 ログインしてユーザーの買い物かご情報を取得する。
useEffect(() => {
loginAndGetCartItem();
},[]);
// ログインするときに必要なユーザー情報
const loginRequestBody: Customer = {
email: "aaaba@gmail.com",
password: "test",
append: function (arg0: string, inputEmail: string): unknown {
throw new Error("Function not implemented.");
},
};
// ログインしてカートアイテムの情報を取得する
const loginAndGetCartItem = async () => {
const response = await CustomerService.signIn2(loginRequestBody);
localStorage.setItem("accessToken", response);
await getCartItem();
};
// ユーザーのカート情報を取得する
const getCartItem = async () => {
let response: CartItemResponse = await CartItemService2.getCartItems2(
localStorage.getItem("accessToken") as unknown as string
);
setCartItemsResponse(response);
};
return(
<div className="Layout">
<div className="ProductViewPosition">
<mainContext.Provider value={{ productResponse, setProductResponse,setCartItemsResponse }}>
<ProductView />
</mainContext.Provider>
</div>
<div className="CartItemViewPosition">
<mainContext.Provider value={{ cartItemsResponse, setCartItemsResponse }}>
<CartItemView />
</mainContext.Provider>
</div>
<style jsx>{`
.Layout{
display:flex;
flex-direction:row;
}
// ProductViewは中心に置く
.ProductViewPosition{
display:flex;
flex-basis:1000px;
flex-wrap:wrap;
// width:500px;
}
// CartItemViewは右側に置く
.CartItemViewPosition{
//align-self:stretch;
}
`}</style>
</div>
);
}
実装後の確認
左側はViewを確認するためブラウザ、右側はDBを確認するためMySqlWorkBenchです。
商品一覧から商品を追加すると、買い物カゴに追加される。
ブラウザとDBともに商品が加えられていることを確認できます。期待した動きができています。
次やること
商品カテゴリーメニューを実装して、カテゴリー毎に商品を表示できるようにする。
以下杏林堂ネットスーパーの参考図
参考
杏林堂ネットスーパー