Build a Product Showcase - Build a Product Showcase

Tell us what’s happening:

Hi! So I am stuck, I can’t figure out why I keep getting the errors I am. I am trying to access the properties in “products” and I keep getting the errors like product.item, product.type or product.title don’t exist on type "Product & Item for example. Any help is appreciated!

Your code so far

<!-- file: index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Product Showcase</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
  <h1>Product Showcase</h1>
  <div class="buttons">
    <button id="all">All</button>
    <button id="books">Books</button>
    <button id="electronics">Electronics</button>
    <button id="clothing">Clothing</button>
  </div>
  <div id="output" class="product-list"></div>
  <script src="index.ts"></script>
</body>
</html>
/* file: styles.css */
body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #0f4c75 0%, #3282b8 100%);
  padding: 40px 20px;
  margin: 0;
  color: #1a1a1a;
  min-height: 100vh;
}

h1 {
  text-align: center;
  margin-bottom: 40px;
  color: #ffffff;
  font-size: 2.5rem;
  font-weight: 600;
  letter-spacing: -0.5px;
}

.buttons {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-bottom: 40px;
  flex-wrap: wrap;
}

button {
  padding: 12px 24px;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  border: none;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.9);
  color: #333;
  transition: all 0.3s ease-in-out;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

button:hover {
  background: white;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

button.active {
  background: #ff9800;
  color: #1a1a1a;
  box-shadow: 0 4px 15px rgba(255, 152, 0, 0.4);
  font-weight: 600;
}

#output {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 24px;
  max-width: 1200px;
  margin: 0 auto;
}

.item {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease-in-out;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.item:hover {
  transform: translateY(-8px);
  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15);
}

.item strong {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: #0f4c75;
  font-weight: 700;
}

.item > div:not(.price) {
  font-size: 16px;
  color: #555;
  line-height: 1.5;
}

.price {
  margin-top: auto;
  font-size: 24px;
  font-weight: 700;
  color: #0f4c75;
  padding-top: 12px;
  border-top: 2px solid #f0f0f0;
}

@media (max-width: 768px) {
  #output {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 16px;
  }

  h1 {
    font-size: 2rem;
  }

  .item {
    padding: 16px;
  }
}

@media (max-width: 480px) {
  #output {
    grid-template-columns: 1fr;
  }

  button {
    padding: 10px 16px;
    font-size: 14px;
  }

  .price {
    font-size: 20px;
  }
}
/* file: index.ts */

interface Item {
 type: "book" | "electronics" | "clothing";
 id: string;
 price: number;
};

interface Book extends Item {
  type: "book";
  title: string;
  author: string;
};

interface Electronics extends Item {
  type: "electronics";
  item: string;
  model: string;
  warranty?: number;
};

 

interface Clothing extends Item {
  type: "clothing";
  item: string;
  brand: string;
  size?: "S" | "M" | "L";
};

type Product = Book | Electronics | Clothing
 
class Collection<T>{
    private items:  Array<T>;

    constructor(i: Array<T>) {
      this.items = i
    }

    getAll() {
     // console.log(this.items)
     return this.items
    }
    filter(callback: (item: T) => boolean): T[]{
      return this.items.filter(callback)
    }
}

const products = new Collection<Product>([
  {
    type: "book", 
    id: "b1", 
    price: 13.99, 
    title: "LOTR Fellowship of the Ring", 
    author:"JRR Tolkien"
  }, 
  {
    type: "book", 
    id: "b2", 
    price: 12.99, 
    title: "The Hobbit", 
    author: "JRR Tolkien"
    }, 
  {
    type: "clothing", 
    id: "c1",
    price: 34.59, 
    item: "Hoodie", 
    brand: "thisbrand", 
    size: "S"
  }, 
  {
    type: "electronics", 
    id: "e1", 
    price: 57.32, 
    item: "headphones", 
    model: "T32", 
    warranty: 2
  }, 
  {
    type: "clothing", 
    id: "c2", 
    price: 45.34, 
    item: "Sweater", 
    brand: "thisbrand", 
    size: "L"
  }, 
  {
    type: "electronics", 
    id: "e2", 
    price: 189.13, 
    item: "Microphone",  
    model: "3Ef2Q22", 
    warranty: 0
  }])

const allItems = products.getAll()
const books = products.filter((item) => item.type === 'book')
const clothing = products.filter((item) => item.type === 'clothing')
const electronics = products.filter((item) => item.type === 'electronics')

const all = document.querySelector<HTMLButtonElement>("#all")
const allBooks = document.querySelector<HTMLButtonElement>("#books")
const allClothing = document.querySelector<HTMLButtonElement>("#clothing")
const allElectronics = document.querySelector<HTMLButtonElement>("#electronics")
const output = document.querySelector<HTMLElement>("#output")

function showProducts () {
  if(output) output.innerHTML = renderProduct(products.getAll())
if(all){
  all.addEventListener("click", () => {
   return renderProduct(allItems)
 })
}  
 if(allClothing){
  return allClothing.addEventListener("click", () => renderProduct(clothing));
} 
 if(allBooks){
  return allBooks.addEventListener("click",() => renderProduct(books));
} 
if(allElectronics){
  return allElectronics.addEventListener("click", () => renderProduct(electronics));
} 
 
} 
const isValid = (product: unknown): product is Item => {
 
  return !!product && typeof product === "object" && "type" in product && "id" in product && "price" in product
  
}
console.log(isValid(allItems)) 


function renderProduct (product: Product[]):string { 
  let itemProperties = '';
  if(isValid(product)){
  
    
   if(product.type === "book"){
    itemProperties += `<div class="item" id="${product.id}">
    <p><strong>Book:</strong> ${product.title} by ${product.author}</p>
    <p class="price">$${product.price}</p>
    </div>`
   }
   if(product.type === "clothing"){
    itemProperties += `<div class="item" id="${product.id}">
    <p><strong>Clothing:</strong> ${product.item} by ${product.brand}</p>
    <p class="price">$${product.price}</p>
    </div>`
   }
   if(product.type === "electronics"){
    itemProperties += `<div class="item" id="${product.id}">
    <p><strong>Clothing:</strong> ${product.item} by ${product.model}</p>
    <p class="price">$${product.price}</p>
    </div>`
   }
  
     
  }
  return itemProperties
  }
  
 



document.addEventListener("DOMContentLoaded", () => {
   showProducts()
   
})

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36

Challenge Information:

Build a Product Showcase - Build a Product Showcase

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-product-showcase/696920c0c98a1ed58eb86293.md at main · freeCodeCamp/freeCodeCamp · GitHub

The errors point to this part of the code:

function renderProduct (product: Product[]):string { 
  let itemProperties = '';
  if(isValid(product)){
  
    
   if(product.type === "book"){
    itemProperties += `<div class="item" id="${product.id}">
    <p><strong>Book:</strong> ${product.title} by ${product.author}</p>
    <p class="price">$${product.price}</p>
    </div>`
   }

The first error says Property 'title' does not exist on type 'Product[] & Item'. product (the function parameter) is typed as Product[] - an array of Products - so it looks like the code is trying to get a title property from the passed in array?

Thanks for the reply, you were right, thank you!