Tenemos hambre de una buena pizza recien hecha. Por supuesto, queremos pedirla online, ya que hablar con una persona solo retrasaria el consumo de pizza.
Hay un solo problema: el constructor de pizzas de nuestra pizzeria local no funciona. Esta vez la pizzeria tiene suerte, porque su cliente de hoy es un desarrollador de React. Tu mision es completar la app para que el constructor de pizzas sea interactivo: los botones deben activar y desactivar ingredientes en la pizza, y el precio debe actualizarse automaticamente.
Esta es la version React del clasico lab DOM Pizza Builder. El HTML y el CSS ya estan portados a componentes JSX; tu trabajo es darle vida con state + props + renderizado condicional.
- Haz Fork de este repo
- Clona este repo
Una vez completado, ejecuta los siguientes comandos:
git add .
git commit -m "Solved lab"
git push origin mainCrea un Pull Request para que tus profesores puedan revisar tu trabajo.
Instala las dependencias y arranca el servidor de desarrollo:
npm install
npm run devAbre la URL que muestra Vite (por defecto http://localhost:5173) en el navegador.
src/
├── App.jsx <- aqui vive el state de la pizza
├── main.jsx
├── index.css <- todos los estilos (no hace falta tocarlos)
├── data/
│ └── ingredients.js <- arrays con las posiciones de cada topping
└── components/
├── Header.jsx <- listo
├── Footer.jsx <- listo
├── Button.jsx <- stub: boton generico reutilizable
├── Controls.jsx <- listo: usa <Button> y recibe handlers desde App
├── Pizza.jsx <- contenedor de la pizza (toppings + crust)
├── Pepperoni.jsx <- stub: rodajas de pepperoni
├── Mushrooms.jsx <- stub: champiñones
├── GreenPeppers.jsx <- stub: pimientos verdes
├── Crust.jsx <- stub: masa, queso y salsa
└── Price.jsx <- panel de precio
El proyecto ya tiene toda la estructura JSX y los estilos CSS terminados. Tu trabajo es completar la logica de estado, props y renderizado condicional en App.jsx, Button.jsx, los componentes de toppings (Pepperoni, Mushrooms, GreenPeppers), Crust.jsx y Price.jsx.
El enfoque que vas a seguir se llama lifting state up: el estado de la pizza vive en App (con useState), se pasa hacia abajo a los hijos a traves de props, y los hijos llaman a funciones (tambien recibidas por props) para modificar el estado. Asi, el estado es la unica fuente de verdad de tu aplicacion.
💡 Los componentes
Controls,HeaderyFooterya estan terminados. No necesitas tocarlos.
Abre src/App.jsx. Veras que hay cinco const placeholder, uno por ingrediente:
const pepperoni = true;
const mushrooms = true;
const greenPeppers = true;
const whiteSauce = true;
const glutenFreeCrust = true;Conviertelos en state usando el hook useState. Crea un useState por ingrediente (cinco en total). Todos arrancan en true porque al cargar la pagina queremos ver la pizza completa:
const [pepperoni, setPepperoni] = useState(true);
const [mushrooms, setMushrooms] = useState(true);
const [greenPeppers, setGreenPeppers] = useState(true);
const [whiteSauce, setWhiteSauce] = useState(true);
const [glutenFreeCrust, setGlutenFreeCrust] = useState(true);💡 Aunque podrias usar un unico
useStatecon un objeto, en este lab usamos cinco estados independientes para practicar el patron y para que cada ingrediente tenga su propia funcion toggle.
<Controls /> ya esta recibiendo cinco props onTogglePepperoni, onToggleMushrooms, etc., pero ahora mismo todas son funciones vacias () => {}. Sustituyelas por handlers inline que llamen al setter correspondiente con el valor invertido:
<Controls
pepperoni={pepperoni}
mushrooms={mushrooms}
greenPeppers={greenPeppers}
whiteSauce={whiteSauce}
glutenFreeCrust={glutenFreeCrust}
onTogglePepperoni={() => { setPepperoni(!pepperoni); }}
onToggleMushrooms={() => { setMushrooms(!mushrooms); }}
onToggleGreenPeppers={() => { setGreenPeppers(!greenPeppers); }}
onToggleWhiteSauce={() => { setWhiteSauce(!whiteSauce); }}
onToggleGlutenFree={() => { setGlutenFreeCrust(!glutenFreeCrust); }}
/>Cada handler invierte el booleano con set...(!...). En React no se muta el estado: se llama al setter con el nuevo valor.
Abre src/components/Button.jsx. El componente recibe cuatro props: color, isPressed, onClick y children. Ahora mismo devuelve null. Hazlo devolver un <button> que:
- Tenga la clase
btn btn-${color}siempre, y ademasactivesolo cuandoisPressedseatrue. - Reciba
onClickdirectamente del prop. - Use
childrencomo contenido visible.
function Button({ children, color, isPressed, onClick }) {
return (
<button
className={`btn btn-${color} ${isPressed ? "active" : ""}`}
onClick={onClick}
>
{children}
</button>
);
}Recarga el navegador. Deberias ver que:
- Los cinco botones aparecen con su color y con la clase
active(todos empiezan marcados). - Al hacer click sobre un boton, este cambia entre activo/inactivo visualmente.
- La pizza todavia no cambia al pulsar los botones: la conectaremos en las siguientes iteraciones.
Cada topping tiene su propio componente que se encarga de pintar las 30+ piezas haciendo .map() sobre un array de posiciones definido en src/data/ingredients.js. Los tres componentes son ahora mismo stubs que devuelven null. Complétalos:
src/components/Pepperoni.jsx:
import { PEPPERONI_POSITIONS } from "../data/ingredients";
function Pepperoni() {
return (
<>
{PEPPERONI_POSITIONS.map((position, index) => (
<section key={`pep-${position}`} className={`pep ${position}`}>
{index + 1}
</section>
))}
</>
);
}
export default Pepperoni;src/components/Mushrooms.jsx:
import { MUSHROOM_POSITIONS } from "../data/ingredients";
function Mushrooms() {
return (
<>
{MUSHROOM_POSITIONS.map((position, index) => (
<section
key={`mushroom-${position}`}
className={`mushroom ${position}`}
>
<div className="cap">{index + 1}</div>
<div className="stem"></div>
</section>
))}
</>
);
}
export default Mushrooms;src/components/GreenPeppers.jsx:
import { GREEN_PEPPER_POSITIONS } from "../data/ingredients";
function GreenPeppers() {
return (
<>
{GREEN_PEPPER_POSITIONS.map((position) => (
<section
key={`green-pepper-${position}`}
className={`green-pepper ${position}`}
/>
))}
</>
);
}
export default GreenPeppers;Abre src/components/Pizza.jsx. Ahora mismo siempre se renderizan los tres toppings:
<div id="pizza">
<GreenPeppers />
<Mushrooms />
<Pepperoni />
<Crust whiteSauce={whiteSauce} isGlutenFree={glutenFreeCrust} />
</div>Envuelve cada topping con {prop && <Componente />} para que solo aparezca cuando su prop sea true:
<div id="pizza">
{greenPeppers && <GreenPeppers />}
{mushrooms && <Mushrooms />}
{pepperoni && <Pepperoni />}
<Crust whiteSauce={whiteSauce} isGlutenFree={glutenFreeCrust} />
</div>Recarga el navegador y haz click en los botones de pepperoni, mushrooms y green peppers. Cada topping debe aparecer y desaparecer de la pizza al pulsar su boton, y el boton debe cambiar entre activo/inactivo.
La salsa y la masa todavia no reaccionan: lo arreglamos en la Iteracion 3.
La salsa y la masa funcionan de forma diferente. En lugar de montar y desmontar elementos, se mantienen siempre en el DOM y lo que cambia es su clase CSS.
Abre src/components/Crust.jsx. Recibe dos props: isGlutenFree y whiteSauce. Ajusta los className:
function Crust({ isGlutenFree, whiteSauce }) {
return (
<section className={`crust ${isGlutenFree ? "crust-gluten-free" : ""}`}>
<section className="cheese"></section>
<section className={`sauce ${whiteSauce ? "sauce-white" : ""}`}></section>
</section>
);
}Recarga el navegador y prueba los botones White sauce y Gluten-free crust:
- Al desactivar White sauce la salsa pasa de blanca a roja (tomate, valor por defecto).
- Al desactivar Gluten-free crust la masa pasa de masa clara (sin gluten) a masa normal.
Abre src/components/Price.jsx. El panel ya muestra el desglose completo y un total hardcodeado a $21. Hay que hacer que se actualice segun el estado de la pizza.
Antes del return, calcula el precio final partiendo de 10 (precio base de la cheese pizza) y sumando el coste de cada ingrediente activo:
| Ingrediente | Prop | Precio |
|---|---|---|
| Pepperoni | pepperoni |
$1 |
| Mushrooms | mushrooms |
$1 |
| Green peppers | greenPeppers |
$1 |
| White sauce | whiteSauce |
$3 |
| Gluten-free crust | glutenFreeCrust |
$5 |
let finalPrice = 10;
if (pepperoni) finalPrice += 1;
if (mushrooms) finalPrice += 1;
if (greenPeppers) finalPrice += 1;
if (whiteSauce) finalPrice += 3;
if (glutenFreeCrust) finalPrice += 5;Muestra finalPrice dentro del <strong> y envuelve cada <li> con {prop && <li>...} para que solo aparezca cuando su ingrediente este activo:
return (
<aside className="panel price">
<h2>Your pizza's price</h2>
<b>$10 cheese pizza</b>
<ul>
{pepperoni && <li>$1 pepperoni</li>}
{mushrooms && <li>$1 mushrooms</li>}
{greenPeppers && <li>$1 green peppers</li>}
{whiteSauce && <li>$3 white sauce</li>}
{glutenFreeCrust && <li>$5 gluten-free crust</li>}
</ul>
<strong>${finalPrice}</strong>
</aside>
);Activa y desactiva los cinco ingredientes y comprueba los totales:
- Con todos los ingredientes activos, el total es $21.
- Con todo desactivado (solo la base), el total es $10.
- Cada
<li>del desglose aparece y desaparece junto a su ingrediente.
Happy coding! ❤️
