After covering Vue.js in September, it is time for React – currently the most popular frontend library for building interactive UIs. React thinks differently from Vue: everything is a function, state flows one way, and JSX looks like HTML in JavaScript. I show the fundamentals from a PHP developer’s perspective and compare the two approaches.
React’s mental model vs Vue
Vue uses a declarative template syntax close to HTML – you can see the relationship to PHP template engines like Twig. React uses JSX – a syntax extension where you write what looks like HTML directly in JavaScript functions. It is a different mindset but becomes natural quickly.
// React component - a function that returns JSX
function ProductCard({ product, onAddToCart }) {
// Everything is a JS function - no options object
const [quantity, setQuantity] = React.useState(1);
const totalPrice = (product.price * quantity).toFixed(2);
return (
// JSX - HTML-like syntax compiled to JavaScript
<div className="product-card">
<h3>{product.name}</h3>
<p>{totalPrice} PLN</p>
<div>
<button onClick={() => setQuantity(q => Math.max(1, q - 1))}>-</button>
<span>{quantity}</span>
<button onClick={() => setQuantity(q => q + 1)}>+</button>
</div>
<button onClick={() => onAddToCart(product.id, quantity)}>
Add to cart
</button>
</div>
);
}
useState – managing local state
import { useState } from 'react';
function Counter() {
// useState returns [currentValue, setter]
// analogous to a PHP class property + setter
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(0)}>Reset</button>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Your name"
/>
<p>Hello, {name || 'stranger'}!</p>
</div>
);
}
useEffect – side effects and API calls
import { useState, useEffect } from 'react';
function ProductList({ categoryId }) {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// useEffect runs after render
// second argument [] means "run once on mount"
// [categoryId] means "run when categoryId changes"
useEffect(() => {
setLoading(true);
setError(null);
fetch(`/rest/V1/products?searchCriteria[filterGroups][0][filters][0][field]=category_id&searchCriteria[filterGroups][0][filters][0][value]=${categoryId}`)
.then(r => r.json())
.then(data => {
setProducts(data.items ?? []);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
// Cleanup function (optional) - runs before next effect or unmount
return () => {
// cancel fetch if component unmounts before response arrives
};
}, [categoryId]); // dependency array
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name} - {p.price} PLN</li>
))}
</ul>
);
}
Custom hooks – extracting reusable logic
// Custom hook - the PHP equivalent of a service class or trait
function useMagentoProduct(productId) {
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!productId) return;
setLoading(true);
fetch(`/rest/V1/products/${productId}`)
.then(r => {
if (!r.ok) throw new Error('Product not found');
return r.json();
})
.then(data => { setProduct(data); setLoading(false); })
.catch(err => { setError(err.message); setLoading(false); });
}, [productId]);
return { product, loading, error };
}
// Use the hook in any component
function ProductDetail({ productId }) {
const { product, loading, error } = useMagentoProduct(productId);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!product) return null;
return (
<div>
<h1>{product.name}</h1>
<p>SKU: {product.sku}</p>
<p>Price: {product.price} PLN</p>
</div>
);
}
React vs Vue – quick comparison
| Aspect | React | Vue 3 |
|---|---|---|
| Syntax | JSX (HTML in JS) | Templates (HTML + directives) |
| Learning curve | Steeper | Gentler |
| State management | hooks (useState, useReducer) | ref, reactive, Pinia |
| Community size | Larger | Large |
| Magento ecosystem | PWA Studio, Hyvä React | Vue Storefront |
| TypeScript support | Excellent | Excellent |
Summary
React’s functional approach with hooks is more powerful than Vue’s Options API but requires a different way of thinking. The key shift for a PHP developer: instead of mutating state directly, you always call the setter. Once you internalise that, React code becomes predictable and easy to follow. If you plan to work with Magento PWA Studio or Hyvä React – React knowledge is essential.
