Card Component for React applications using the "Compound Component" and "Function as a child" patterns.
Using npm:
$ npm install jumagu-product-card
Using bower:
$ bower install jumagu-product-card
Using yarn:
$ yarn add jumagu-product-card
Using pnpm:
$ pnpm add jumagu-product-card
To use the product card, you must import the <ProductCard />
and provide the mandatory properties: product
and children
. The product one must satisfy the Product interface:
interface Product {
id: string;
title: string;
img?: string;
}
Children should be the other three components included in the package, which are: <ProductImage />
, <ProductTitle />
, <ProductButtons />
. These should be there for the correct functioning of the component. However, it is possible to include any other valid React component or HTML element.
The child components communicate with the parent through a context shared by <ProductCard />
, so there is no need to send extra properties to the children. However, for the case of the <ProductImage />
and <ProductTitle />
the optional img
and title
propeties can be sent respectively to overwrite the values of the product that is initially sent to the <ProductCard />
.
import {
type Product,
ProductCard,
ProductImage,
ProductTitle,
ProductButtons,
} from "jumagu-product-card";
const product: Product = {
id: "1",
title: "Coffee Mug",
img: "./coffee-mug.png",
};
export const Products = () => {
return (
<ProductCard product={product}>
<ProductImage img="https://example-image.com" />
<ProductTitle title="Example title" />
<ProductButtons />
{/* Other components */}
<Price price={45}>
</ProductCard>
);
};
Another way to use child components is to extract them from the <ProductCard />
as properties of an object, as follows:
<ProductCard product={product}>
<ProductCard.Image />
<ProductCard.Title />
<ProductCard.Buttons />
</ProductCard>
Additionally, custom styles can be passed to the components, by means of props, as shown below:
<ProductCard
product={product}
style={{ backgroundColor: "rgb(56, 56, 56)", color: "white" }}
>
{/* You can use the style property */}
<ProductCard.Image
style={{
margin: 10,
borderRadius: 10,
width: "calc(100% - 20px)",
}}
/>
{/* You can also use the className */}
<ProductCard.Title className="custom-title" />
{/* Or you can use both :) */}
<ProductCard.Buttons
className="custom-btn"
style={{
backgroundColor: "salmon",
width: "fit-content",
borderRadius: 5,
}}
/>
</ProductCard>
In case you need to manipulate the state of the card, such as controlling the quantity of a product being carried, you can use additional properties to control this: quantity
and onChange
. These properties can control the state of the card according to the needs, for example with a custom hook, as shown below:
import {
ProductCard,
ProductImage,
ProductTitle,
ProductButtons,
useShoppingCart,
} from "jumagu-product-card";
import { products } from "../data/products.data";
export const ShoppingCart = () => {
const { shoppingCart, handleAddToCart } = useShoppingCart(); // ! This custom hook is included in the package
return(
<>
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
className="bg-dark text-white"
quantity={shoppingCart[product.id]?.quantity || 0}
onChange={handleAddToCart}
>
<ProductImage className="custom-image" />
<ProductTitle className="text-bold" />
<ProductButtons className="custom-buttons" />
</ProductCard>
))}
</>
);
};
Now, the recommended way to use the <ProductCard />
is to pass it the initialValues
property to initialize the values as required and, instead of using the Compound Component pattern, the pattern "function as a child" is used, which is way better for reusability, fexibility and state sharing.
This function receives an object that contains the product, quantity, maxQuantity, isMaxQuantityReached, incrementBy, and reset properties, and it returns a JSX fragment that contains ProductImage, ProductTitle, and ProductButtons components. Here it is aslo possible to include any other valid React component or HTML element.
<ProductCard
product={products[0]}
className="bg-dark text-white"
initialValues={{ quantity: 5, maxQuantity: 10 }}
>
{({ quantity, maxQuantity, incrementBy, reset }) => (
<>
<ProductImage className="custom-image" />
<ProductTitle className="text-bold" />
<ProductButtons className="custom-buttons" />
<div>
<button onClick={() => incrementBy(-2)}>-2</button>
<button onClick={reset}>Reset</button>
<button onClick={() => incrementBy(2)}>+2</button>
<span>
{quantity} / {maxQuantity}
</span>
</div>
</>
)}
</ProductCard>