IMPERATIVE ハンドルフックと NEXT JS CART 機能を使用する

8870 ワード

このチュートリアルでは、react の useImperativeHandle について学習し、このフックを使用して単純なカート機能を構築します.

設定



を使用して反応プロジェクトを作成します

create react app command
Component という名前のフォルダーを作成し、そのフォルダーに「Cart.tsx」というファイルを作成します.

import { FC, forwardRef, useImperativeHandle, useState } from "react";

interface ICartProps {
  isOpen: boolean;
  toggleCartOpen: () => void;
  ref: any;
}

const Cart:FC<ICartProps> = forwardRef((props, cartRef: any) => {
    const [cart, setCart] = useState([] as any[]);

    useImperativeHandle(cartRef, () => ({
      addToCart(product: any) {
        let newCart = cart;
        let obj = newCart.find((item: any, i: number) => {
          if(item.name === product.name){
            newCart[i] = product;
            setCart(newCart);
            return true;
          }
        })
        if(!obj){
          setCart(newCart.concat(product));
          setTotalAmount(newCart.concat(product).reduce(
            (acc: any, item: any) => acc + item.price * (item.quantity || 1),
            0,
          ));
        }
      },
    }));


    const removeFromCart = (i: number) => {
      let newCart = cart;
      setCart(newCart.filter((item: any, index: number) => index !== i));
    };

    const changeProductQuantity = (i: number, quantity: number) => {
      let newCart = cart;
      newCart[i].quantity = quantity;
      setCart(newCart);

    }

    return (
        <div
          className="flex flex-col justify-between w-20 h-10 fixed bottom-8 right-16"
        >
            <div>
            {cart.map((item: any, i: any) => (
            <div
                className="flex flex-col w-[95%] py-1 border-b border-solid border-gray-800"
                key={i}
            >
                <div className="flex flex-row justify-between">
                    <span
                        className="text-black text-base"
                    >
                        {item.name.toUpperCase()}
                    </span>

                    <button
                        className="py-1 px-2 text-center text-red-600 cursor-pointer"
                        onClick={() => removeFromCart(i)}
                    >
                        X
                    </button>
                </div>

                <div className="flex flex-row justify-between">
                    <p
                        className="text-gray-800 m-0"
                    >    
                        N{item.price} x 
                    </p>
                    <input
                        type="number"
                        className="w-[20%] py-1 px-2 text-center bg-gray-200 rounded-md"
                        defaultValue={item.quantity || 1}
                        onChange={(e: any) => onProductQuantityChange(i, e.target.value)}
                    />

                </div>
            </div>
            ))}


        </div>            
        </div>
    )
});

export default Cart




上記のファイルでは、最初に、コンポーネントが期待する props とそのタイプを宣言するコンポーネントのインターフェイスを定義します.

コンポーネントを forwardRef でラップしていることに注意してください.これは、親コンポーネントから子コンポーネントに ref を渡すことができる別の反応フックです.これは、useImperativeHandle フックを機能させるために必要です.

次に、カートのデータを保存する状態を宣言します.

この実装のアイデアは、Cart.tsx コンポーネント内でカート データを保持する状態と、カート内の商品の追加、削除、および変更のための関数を宣言することです.しかし、このコンポーネントの外から addToCart 関数を呼び出したい (例えば、製品データが利用可能になる場所)、コンポーネントの外からこの関数だけを呼び出せるようにしたいので、それを useImperativeHandle 関数でラップします.
useImperativeHandle は、 forwardRef ハンドルを使用して親コンポーネントから渡される ref を受け取り、オブジェクトを返します (例: addToCart 関数).
addToCart 関数は、商品がすでにカートに入っているかどうかを確認し、そうでない場合はカートの状態に追加します.

他の関数は、カートからアイテムを削除し、カート内のアイテムの数を変更するために使用されます.

Components フォルダーに ProductComponent ファイルを作成します.
次のコードをファイルに貼り付けます

import Image from 'next/image
import { FC } from 'react'
import { HiShoppingCart } from 'react-icons/hi'

interface IProductComponentProps {
    key: any;
    result: any;
    addToCart: (product: any) => void;
}

const ProductComponent: FC<IProductComponentProps> = (props) => {

    return (
        <div
            className='flex flex-col bg-white rounded-md w-[70%] md:w-[90%] lg:w-[100%] mx-auto mb-8 
             p-4 lg:flex-row'
        >
            <Image 
                src="https://via.placeholder.com/100"
                alt="product image"
                width={100}
                height={200}
                className='rounded-md justify-center mr-4 h-auto cursor-pointer'
            />

            <div 
                className="flex flex-col justify-between ml-3 pt-2"
            >
                <h3 
                    className='text-lg font-mono'
                >
                    {props.result.name}
                </h3>
                <p 
                    className='text-gray-500 text-base line-clamp-2 mb-2'
                >
                    {props.result.description}
                </p>
                <div 
                    className='flex flex-row justify-between mb-2'
                >
                    <p 
                        className='text-base text-green-700 font-semibold'
                    >
                        {props.result.price }
                    </p>

                </div>

                <button
                    className="bg-orange-500 hover:bg-orange-700 text-white font-bold py-2 px-2 w-[50%] 
                    flex justify-center mx-auto rounded-md"
                    onClick={() => props.addToCart(props.result)}
                >
                    <HiShoppingCart className="text-lg text-center" />
                </button>
            </div>
        </div>
    )
}

export default ProductComponent




上記のファイルでは、ProductComponent プロパティのインターフェイスを宣言しており、これらのプロパティを使用してコンポーネントを作成しています.

次に、コンポーネント フォルダーに Results.tsx ファイルを作成します.
Results.tsx ファイルでは、Cart.tsx コンポーネントと product コンポーネントをインポートします.これは、useImperativeHandle フックをトリガーする場所です.

import { useRef, useState } from "react";
import Cart from "../Components/Cart";

const results = () => {
    const cartRef = useRef({});

    const addToCart = (newProduct:any) => {
        cartRef.current?.addToCart(newProduct);
    }

    const [cartOpen, setCartOpen] = useState(false);

 return (
        <div>
        <Cart 
                isOpen={cartOpen}
                toggleCartOpen={openOrCloseCart}
                ref={cartRef}
            />

      </div>

}


上記のファイルでは、「cartRef」という名前の ref を定義しています.これは、cart コンポーネントで useImperativeHandle をトリガーするために props として cart コンポーネントに渡される ref です.
このファイルで定義している addToCart 関数は、コンポーネントに渡す ref で現在のメソッドを呼び出すことにより、useImperativeHandle によって返される Cart コンポーネントの addToCart 関数をトリガーすることに注意してください.

ダミーデータを渡して ProductComponent を表示しましょう

import { useRef, useState } from "react";
import Cart from "../Components/Cart";

const results = () => {
    const cartRef = useRef({});

    const addToCart = (newProduct:any) => {
        cartRef.current?.addToCart(newProduct);
    }

    const [cartOpen, setCartOpen] = useState(false);

     const products = [
    {
      name: 'SHIRT',
      description: 'Product Description',
      image: 'https://via.placeholder.com/100',
      price: '$100',
      discount_rate: '10%',
      quantity: 2,
    },
    {
      name: 'Attack on Titan',
      description: 'Manga',
      image: 'https://via.placeholder.com/100',
      price: '$100',
      discount_rate: '10%',
    quantity : 2
    },
    {
      name: 'Demon slayer ',
      description: 'Manga',
      image: 'https://via.placeholder.com/100',
      price: '$100',
      discount_rate: '10%',
      quantity : 1
    },
    ]



 return (
        <div>
        <Cart 
                isOpen={cartOpen}
                toggleCartOpen={openOrCloseCart}
                ref={cartRef}
            />

        {
          products.map((product:any) => (
            <ProductComponent 
              key={product.name}
              result={product} 
              addToCart={addToCart}
            />
          ))
        }
      </div>
}