Skip to main content

Overview

This guide provides solutions to common issues you might encounter while developing or deploying Q-Sopa.

API Connection Issues

Problem: Categories fail to load and you see “Cargando…” indefinitely or an error message.Diagnosis: Check the browser console for error messages.Solutions:
  1. Verify API endpoint in src/services/api.js:
const BASE_URL = "https://apiqsp-production.up.railway.app";
  1. Test the API directly:
curl https://apiqsp-production.up.railway.app/categories
  1. Check network requests in browser DevTools (Network tab):
  • Look for failed requests (red status codes)
  • Verify the request URL is correct
  • Check response status (should be 200)
  1. Common fixes:
// Add better error handling
export const getCategorias = async () => {
  try {
    const res = await fetch(`${BASE_URL}/categories`);
    if (!res.ok) {
      console.error('Response status:', res.status);
      throw new Error(`HTTP error! status: ${res.status}`);
    }
    return res.json();
  } catch (error) {
    console.error('Failed to fetch categories:', error);
    throw error;
  }
};
Problem: Categories load but products don’t appear when clicking a category.Check these in src/pages/Menu.jsx:
  1. Verify state management:
const [products, setProducts] = useState([]);
const [activeCategoryId, setActiveCategoryId] = useState(null);
  1. Check the useEffect dependency:
useEffect(() => {
  if (activeCategoryId === null) return;
  
  setLoading(true);
  setError(null);
  
  getProductsByCategory(activeCategoryId)
    .then((data) => {
      console.log('Products loaded:', data); // Add this for debugging
      setProducts(data);
    })
    .catch((err) => {
      console.error('Error loading products:', err);
      setError(err.message);
    })
    .finally(() => setLoading(false));
}, [activeCategoryId]);
  1. Verify API returns correct data format: The API should return an array of products:
[
  {
    "id": 1,
    "name": "Classic Burger",
    "price": 12.99,
    "imageUrl": "https://...",
    "badge": "Popular"
  }
]
  1. Check category ID is being passed correctly:
// In Categories.jsx
const handleClick = (id) => {
  console.log('Category clicked:', id); // Debug log
  setActive(id);
  onCategoryChange?.(id); // Make sure this passes the ID
};
Problem: Requests time out or take too long.Solution: Add timeout handling:
// src/services/api.js
const fetchWithTimeout = async (url, timeout = 5000) => {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    clearTimeout(id);
    return response;
  } catch (error) {
    clearTimeout(id);
    if (error.name === 'AbortError') {
      throw new Error('Request timeout');
    }
    throw error;
  }
};

export const getCategorias = async () => {
  const res = await fetchWithTimeout(`${BASE_URL}/categories`);
  if (!res.ok) throw new Error("Error al cargar categorías");
  return res.json();
};

CORS Errors

Error: “Access to fetch at ‘https://…’ from origin ‘http://localhost:5173’ has been blocked by CORS policy”Cause: The API server doesn’t allow requests from your domain.Solutions:If you control the API, add CORS headers:Express.js:
const cors = require('cors');

app.use(cors({
  origin: ['http://localhost:5173', 'https://your-production-domain.com'],
  credentials: true
}));
Fastify:
await fastify.register(require('@fastify/cors'), {
  origin: ['http://localhost:5173', 'https://your-production-domain.com']
});

2. Development Proxy

Use Vite’s proxy feature for development. Update vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'https://apiqsp-production.up.railway.app',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})
Then update src/services/api.js:
const BASE_URL = import.meta.env.DEV 
  ? '/api' 
  : 'https://apiqsp-production.up.railway.app';

3. Verify Request Headers

Check if credentials are needed:
export const getCategorias = async () => {
  const res = await fetch(`${BASE_URL}/categories`, {
    mode: 'cors',
    credentials: 'include' // Only if API requires cookies
  });
  if (!res.ok) throw new Error("Error al cargar categorías");
  return res.json();
};
Problem: CORS preflight requests are being rejected.Solution: Ensure your API handles OPTIONS requests:
// Express.js example
app.options('*', cors());

// Or manually:
app.options('*', (req, res) => {
  res.header('Access-Control-Allow-Origin', req.headers.origin);
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(200);
});

Build Errors

Error: Error: Cannot find module '@vitejs/plugin-react'Solutions:
  1. Clean install dependencies:
rm -rf node_modules package-lock.json
npm install
  1. Verify package.json:
{
  "devDependencies": {
    "@vitejs/plugin-react": "^5.1.1",
    "vite": "^7.3.1"
  }
}
  1. Check Node version:
node --version  # Should be 16+ or 18+
  1. Clear npm cache:
npm cache clean --force
npm install
Problem: Tailwind classes are not being applied.Note: Q-Sopa uses Tailwind CSS v4. Check configuration:
  1. Verify installation:
npm list tailwindcss
  1. Check import in src/index.css:
@import "tailwindcss";
  1. Ensure CSS is imported in src/main.jsx:
import './index.css'
  1. Rebuild:
npm run build
Error: “JavaScript heap out of memory”Solution: Increase Node.js memory limit:
# Unix/Mac
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

# Windows (PowerShell)
$env:NODE_OPTIONS="--max-old-space-size=4096"
npm run build
Or update package.json:
{
  "scripts": {
    "build": "NODE_OPTIONS='--max-old-space-size=4096' vite build"
  }
}

Development Server Issues

Error: Port 5173 is already in useSolutions:
  1. Kill the process using the port:
# Find process
lsof -i :5173  # Mac/Linux
netstat -ano | findstr :5173  # Windows

# Kill process
kill -9 <PID>  # Mac/Linux
taskkill /PID <PID> /F  # Windows
  1. Use a different port in vite.config.js:
export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000
  }
})
  1. Let Vite choose automatically:
export default defineConfig({
  plugins: [react()],
  server: {
    strictPort: false
  }
})
Problem: Changes don’t reflect immediately in the browser.Solutions:
  1. Check file watching in vite.config.js:
export default defineConfig({
  plugins: [react()],
  server: {
    watch: {
      usePolling: true  // Enable for Docker/VMs
    }
  }
})
  1. Clear browser cache:
  • Open DevTools
  • Right-click refresh button
  • Select “Empty Cache and Hard Reload”
  1. Restart dev server:
# Stop with Ctrl+C, then:
npm run dev
Problem: Server takes long to start or respond.Solutions:
  1. Optimize dependencies in vite.config.js:
export default defineConfig({
  plugins: [react()],
  optimizeDeps: {
    include: ['react', 'react-dom']
  }
})
  1. Clear Vite cache:
rm -rf node_modules/.vite
npm run dev
  1. Disable source maps for faster builds:
export default defineConfig({
  plugins: [react()],
  build: {
    sourcemap: false
  }
})

Component Rendering Issues

Problem: Product images show broken image icons.Diagnosis:
  1. Check image URLs in browser DevTools Network tab
  2. Verify API response contains valid imageUrl fields
Solutions:
  1. Add error handling in ProductCard.jsx:
import { useState } from 'react';
import "./ProductCard.css";

export default function ProductCard({ title, price, image, badge }) {
  const [imgError, setImgError] = useState(false);
  const fallbackImage = "https://via.placeholder.com/300x200?text=No+Image";
  
  return (
    <div className="product-card">
      {badge && <div className="badge">{badge}</div>}
      
      <img 
        src={imgError ? fallbackImage : image} 
        alt={title}
        onError={() => setImgError(true)}
      />
      
      <div className="product-info">
        <h4>{title}</h4>
        <span>${price}</span>
      </div>
      
      <button className="product-btn">Ingredientes</button>
    </div>
  );
}
  1. Check CORS for images: Some CDNs block cross-origin image requests
  2. Verify image paths if using local assets:
import placeholderImg from '../../assets/placeholder.jpg';
Problem: Category icons appear as text or don’t render.Solution: Verify Material Symbols is loaded in index.html:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Q-Sopa</title>
    
    <!-- Material Symbols -->
    <link rel="stylesheet" 
          href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
Check in Categories.jsx usage:
<span className="material-symbols-outlined">{cat.icon}</span>
Problem: Hamburger menu doesn’t open on mobile.Check these in src/components/Categories/Categories.jsx:
  1. State is defined:
const [drawerOpen, setDrawerOpen] = useState(false);
  1. Button click handler:
<button
  className="drawer-toggle"
  onClick={() => {
    console.log('Drawer toggled'); // Debug log
    setDrawerOpen(true);
  }}
  aria-label="Abrir menú de categorías"
>
  <span />
  <span />
  <span />
</button>
  1. CSS classes are correct:
<nav className={`drawer-panel ${drawerOpen ? "open" : ""}`}>
  1. Overlay closes drawer:
<div
  className={`drawer-overlay ${drawerOpen ? "open" : ""}`}
  onClick={() => setDrawerOpen(false)}
/>
  1. Test on actual mobile device, not just browser resize (touch events may differ)
Problem: Active category doesn’t show visual feedback.Solution: Verify state comparison in Categories.jsx:
const [active, setActive] = useState(null);

// Make sure ID types match (number vs string)
const handleClick = (id) => {
  console.log('Setting active:', id, typeof id);
  setActive(id);
  onCategoryChange?.(id);
};

// Render:
<button
  key={cat.id}
  className={`category-btn ${active === cat.id ? "active" : ""}`}
  onClick={() => handleClick(cat.id)}
>
If IDs don’t match (e.g., 1 vs "1"), convert them:
setActive(Number(id));

State Management Issues

Problem: State changes don’t trigger re-renders.Common causes:
  1. Mutating state directly:
// ❌ Wrong
products.push(newProduct);
setProducts(products);

// ✅ Correct
setProducts([...products, newProduct]);
  1. Async setState:
// ❌ Won't work as expected
setProducts([]);
setLoading(false); // Might not reflect the state change

// ✅ Correct
setProducts([]);
setTimeout(() => setLoading(false), 0);
  1. Missing dependencies in useEffect:
// ❌ Missing dependency
useEffect(() => {
  fetchProducts(categoryId);
}, []); // categoryId is missing

// ✅ Correct
useEffect(() => {
  fetchProducts(categoryId);
}, [categoryId]);
Problem: Component keeps re-rendering indefinitely.Cause: State update in render or missing useEffect dependencies.Solution:
// ❌ Wrong - causes infinite loop
function Menu() {
  const [data, setData] = useState([]);
  setData([...]); // Direct call in render
}

// ✅ Correct
function Menu() {
  const [data, setData] = useState([]);
  
  useEffect(() => {
    setData([...]);
  }, []); // Only run once
}
Check useEffect dependencies:
// ❌ Object causes re-render every time
const config = { key: 'value' };
useEffect(() => {
  // ...
}, [config]); // New object each render

// ✅ Use primitive values or useMemo
useEffect(() => {
  // ...
}, [config.key]);

Browser Compatibility

Problem: Features don’t work in Safari, older Edge, etc.Solution: Check browser support and add polyfills if needed:
  1. Check compatibility:
  • Optional chaining (?.) requires modern browsers
  • Promise.finally() needs polyfills for IE11
  1. Target specific browsers in package.json:
{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead",
    "not ie 11"
  ]
}
  1. Use Vite’s legacy plugin for older browsers:
npm install --save-dev @vitejs/plugin-legacy
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    react(),
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
})

Performance Issues

Problem: Application takes long to load.Solutions:
  1. Analyze bundle size:
npm run build
# Check the dist/ folder size
  1. Implement code splitting:
import { lazy, Suspense } from 'react';

const Menu = lazy(() => import('./pages/Menu'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Menu />
    </Suspense>
  );
}
  1. Optimize images:
  • Use WebP format
  • Compress images (TinyPNG, Squoosh)
  • Implement lazy loading
  1. Enable compression on hosting platform (gzip/brotli)
Problem: Application slows down over time.Solution: Clean up effects in Categories.jsx:
useEffect(() => {
  const handleKey = (e) => {
    if (e.key === "Escape") setDrawerOpen(false);
  };
  window.addEventListener("keydown", handleKey);
  
  // ✅ Cleanup function
  return () => window.removeEventListener("keydown", handleKey);
}, []);
Cancel fetch requests on unmount:
useEffect(() => {
  const controller = new AbortController();
  
  fetch(url, { signal: controller.signal })
    .then(data => setData(data))
    .catch(err => {
      if (err.name !== 'AbortError') {
        console.error(err);
      }
    });
  
  return () => controller.abort();
}, [url]);

Getting Help

If you’re still experiencing issues:
  1. Check browser console for error messages
  2. Review network requests in DevTools
  3. Test API endpoints directly with curl or Postman
  4. Verify environment variables are set correctly
  5. Check package versions match the documentation
Enable detailed logging for debugging:
// src/services/api.js
const DEBUG = true;

export const getCategorias = async () => {
  if (DEBUG) console.log('Fetching categories from:', `${BASE_URL}/categories`);
  
  const res = await fetch(`${BASE_URL}/categories`);
  
  if (DEBUG) console.log('Response:', res.status, res.statusText);
  
  if (!res.ok) throw new Error("Error al cargar categorías");
  
  const data = await res.json();
  if (DEBUG) console.log('Categories data:', data);
  
  return data;
};

Next Steps

Customization

Customize the application

Deployment

Deploy to production

Build docs developers (and LLMs) love