symfony/ux-react
Official Symfony UX React integration lets you render React components directly from Twig, pass props, and mount interactive UI inside Symfony pages. It pairs with Webpack Encore for asset building, making React easy to add to traditional server-rendered Symfony applications.
symfony/ux-react is a Symfony UX bundle, not a Laravel package. It has no ServiceProvider for Laravel's package discovery, no config/ux_react.php, and its whole surface area is Twig (a ReactComponent Twig component, a react_component() function) plus AssetMapper/Webpack Encore integration. So for a Laravel developer the first decision is not "how do I install it" but "should I be here at all."
laravel/react-starter-kit + Vite, or inertiajs/inertia-laravel if you want server-driven props. You get typing-free @vite, HMR out of the box, and Blade instead of Twig. Everything ux-react does for Symfony, Inertia does for Laravel with a fraction of the friction.composer require symfony/ux-reactimportmap.php, no Node) or Webpack Encore (encore dev-server). They are not mixed.php bin/console importmap:require react react-dom. Encore: npm i react react-dom.assets/react/controllers/Hello.jsx and render it: <twig:ReactComponent component="Hello" props="{{ { name: 'world' } }}" />The first thing to look at is assets/controllers.json (AssetMapper) or the registerReactControllerComponents(...) call in assets/bootstrap.js (Encore) — that is the registration boundary for every component you'll ever render. If a component "doesn't exist," the answer is almost always there.
For v3.6.0 specifically: nothing changes here. The release is a README wording fix, a dev-tooling bump, and a compatibility fix for symfony/asset-mapper 8.2+. If your project sat pinned on asset-mapper 8.1 because of the conflict, 3.6.0 unblocks the upgrade.
Twig-component rendering (the normal path). Each <twig:ReactComponent> is an independent React root. Props come from Twig and are JSON-injected, so your controllers are pure functions of server data. The idiomatic pattern is "thin component, fat server": compute the payload in PHP, serialize to a plain array, render. Do not try to lift state out of React and back into Twig except via a full page reload.
Prop serialization is your real integration layer. Pass DTOs flattened with Symfony's Serializer ($serializer->normalize($dto, 'json')) rather than entities. If you find yourself wanting |raw or |json_encode in the props expression, you've got a serialization bug upstream — fix it there, because the failure mode is a silent [object Object] or an uncaught JSON parse, not a helpful exception.
Multi-root pages. You'll often have three or four components on one page (a filter bar, a data table, a modal). They cannot share React context. Standard workarounds: (a) initialize a global store (Zustand/Redux) from a single bootstrapped prop, (b) use browser events/CustomEvent between roots, or (c) collapse them into one root component and let React own the whole section. Pattern (c) ages best; pattern (a) is the pragmatic first step.
Code splitting and lazy loading. React.lazy(() => import('./Heavy')) works, but only if your bundler emits a real chunk. With Encore this is transparent. With AssetMapper you need the dynamic import to resolve through the importmap (add the target as an importmap entry), otherwise you get a runtime Failed to fetch dynamically imported module that only reproduces in prod after importmap:install.
Interop with the rest of Symfony UX. Components are usually mounted from a Stimulus controller or a Turbo Frame. Two rules: mount on connect() and unmount on disconnect() (Turbo will cache and restore DOM), and treat every Turbo navigation as a fresh mount — do not cache React state across frames unless you deliberately hoist it. If you're also using ux-live/LiveComponents, keep them out of the React subtree; hydration conflicts there are painful to debug.
What to borrow for Laravel. Even if you never install this, two ideas transfer: server-normalized props as the contract (mirror it with Inertia's ->through() or API Resources), and per-root mounting with explicit unmount (mirror it in any Blade + createRoot you hand-roll).
symfony/asset-mapper >=8.2, upgrade ux-react to 3.6.0 rather than downgrading asset-mapper. The other two changelog entries (README version placeholder, tooling deps) are non-events — no runtime code, no API surface, no deprecations.component="hello" vs Hello, missing .jsx in the glob, or a file outside assets/react/controllers produces a client-side "component not found" with no server error. Debug server-side with bin/console debug:twig to confirm the Twig component is registered, then verify the glob root.fetch-on-mount will fire twice and look like a server bug. It isn't — don't "fix" it by guarding with a module-level flag.JSON.stringify. DateTime, entities with circular refs, and closures all fail here, and Symfony's error surface is unhelpful. The 3.x default json_encode flags also mean float precision and NAN quietly become null.jsx-dev-runtime resolution errors. Pin React in package.json and check peer deps before blaming the bundle.services.yaml rather than forking — this is the one place the Symfony DI approach genuinely beats Laravel's facade-centric style.importmap:audit (AssetMapper) or encore dev-server output → browser console for the component-name miss → debug:twig for registration → then the props payload. Nine times out of ten the bug is step two or four, not the bundle.How can I help you explore Laravel packages today?