Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mingle Laravel Package

ijpatricio/mingle

Use React or Vue components inside your Laravel Livewire app. MingleJS mounts each JS component as an island of interactivity, passing backend data to the frontend so server actions can be called directly with $wire.addTodo(todo).

View on GitHub
Deep Wiki
Context7

Getting Started

MingleJS is a bridge, not a framework — the fastest path is to get one island mounting end-to-end before you think about architecture.

Prerequisites (verify before you install):

  • Laravel 10/11 with Livewire already working (this package is additive; it assumes Livewire is your baseline).
  • Vite via laravel-vite-plugin. Mix is mentioned as possible but the docs and the package's own examples assume Vite — don't fight it on day one.
  • Vue 3, or React 16+ with @vitejs/plugin-react.

Install in four moves:

composer require ijpatricio/mingle
npm install vue            # or: npm install react react-dom && npm i -D @vitejs/plugin-react

Then make sure your component source directory is actually in the Vite input glob:

// vite.config.js
laravel({ input: ['resources/js/app.js', 'resources/js/mingle/**/*.{js,vue,jsx,tsx}'] })

The second half of the install — whatever config publish / provider registration / JS entry the package expects — is on the Getting Started page at https://minglejs.unitedbycode.com. Read that page and the "Livewire to JS data" page before writing code; those two pages define the prop contract and the $wire action API, which are the only things you can get durably wrong.

Where to look first, beyond the docs:

  • The rendered div in your browser's element inspector after the first mount — it tells you what props actually crossed the wire.
  • The package's src/ directory for the directive implementation, so you know exactly what it emits server-side.
  • The GitHub release notes, since the docs site and the code occasionally drift.

First use case: pick a widget with local state that genuinely wants a JS framework — a filter sidebar with multi-select facets, a tag/chip input, a small kanban column, a chart panel. Do not start with a page that also carries critical Livewire behaviour; you want a failure here to be boring.

// app/Livewire/TodoBoard.php
public array $todos = [];

public function addTodo(string $text): void
{
    $this->todos[] = ['id' => uniqid(), 'text' => $text, 'done' => false];
}
{{-- resources/views/livewire/todo-board.blade.php --}}
<div>
    <h1>Board</h1>
    @mingle('TodoList', ['todos' => $todos])
</div>
<!-- resources/js/mingle/TodoList.vue -->
<script setup>
const props = defineProps({ todos: { type: Array, default: () => [] } })
</script>

<template>
  <ul><li v-for="t in props.todos" :key="t.id">{{ t.text }}</li></ul>
  <button @click="$wire.addTodo('New item')">Add</button>
</template>

The three-point smoke test — run this every time and you'll never waste an hour on the wrong layer:

  1. The server HTML contains the wrapper div.
  2. The component mounts with no console errors.
  3. $wire.addTodo('x') from a button round-trips and the prop updates.

If all three pass, the seam works. Everything after that is the same pattern with more props.

Implementation Patterns

Island-per-component, and keep the count small. Each JS component gets one wrapping Livewire component and one @mingle(...) call. The value proposition is selective application — virtualized tables, chart editors, drag-and-drop, real-time widgets. CRUD forms, auth, navigation, and anything SEO-bearing stay in Livewire/Blade. A practical ceiling: one to three islands per page. Every island is a place where two render models meet, and seams have a cost.

The prop contract is your API — treat it like one. Pass primitives and arrays only. Serialize in the Livewire component before the directive:

@mingle('OrderTable', [
    'orders'   => $this->orders->map->only(['id','total','status'])->values()->all(),
    'currency' => config('app.currency'),
    'canEdit'  => auth()->user()->can('edit-orders'),
])

Note the canEdit flag: authorization decisions belong server-side, and passing the result down is cleaner than calling $wire.can(...) from every render. Keep the prop names stable — they're now a cross-team contract.

$wire actions instead of axios, by default. This is the package's biggest day-to-day win: you reuse Livewire's validation, middleware, session, and policies instead of reimplementing them as REST endpoints.

await $wire.saveDraft({ title, body })   // returns the Livewire action's return value
$wire.$refresh()                          // force a server round-trip

Reach for axios/fetch only for genuinely external APIs (third-party services, webhooks, an existing non-Livewire endpoint). Don't build a parallel JSON API for your own app just because you're now in JS.

Keep local state local, lift shared state deliberately. Pinia or a React context inside one island is fine — that state dies when the island unmounts. Cross-island state is not free. Your options, in order of preference:

  1. Lift to the parent Livewire component and pass down as props.
  2. Browser events (window.dispatchEvent) for loose coordination between sibling islands.
  3. Persist server-side and refetch.

Don't assume a global store spans islands. It doesn't.

wire:key on islands inside loops. If you render a Mingle component inside a @foreach over Livewire-rendered rows, key the wrapper or Livewire will happily reuse DOM nodes and you'll get stale props in the second island:

@foreach ($rows as $row)
    <div wire:key="row-{{ $row->id }}">
        @mingle('RowEditor', ['row' => $row->only('id','name')])
    </div>
@endforeach

Hybrid page composition, written down. Before island #3, document the convention: props shape, $wire actions exposed, loading/error fallback, file location. Something short in the repo:

resources/js/mingle/
  OrderTable.vue      props: { orders, currency, canEdit }  actions: deleteOrder, reorder
  OrderFilters.vue    props: { facets }                     actions: applyFilters

This is the cheapest thing you can do to keep a hybrid codebase navigable for the next person.

Feature-flag the rollout. Wrap each island in a config/env toggle in the Blade layer. You get a canary, a kill switch, and a clean revert, and it forces you to keep the Blade fallback alive:

@if (config('features.islands.order_table'))
    @mingle('OrderTable', $props)
@else
    @include('partials.orders-table-static')
@endif

Testing the seam, not the pieces. Vitest/Pest-level tests on the Vue component in isolation will never catch the bugs you'll actually hit. Prioritise three integration checks:

  • Mounts with the correct props (assert on the rendered DOM after mount).
  • Parent Livewire re-renders (validation error, $poll) while the island is active — does the prop update arrive and re-render correctly, or does local state blow it away?
  • $wire.action() round-trip returns and reflects.

Livewire's own JS testing helpers plus a browser test (Dusk/Playwright) on one happy path covers most of it.

In Filament. The same pattern applies inside panels and custom resources — a Mingle island is just a Blade partial from Filament's perspective. Useful for embedding a charting or drag-and-drop widget that Filament's stock components don't cover.

Gotchas and Tips

Hydration and re-render are the #1 failure surface. The server ships an empty div; the client mounts into it. Every time the parent Livewire component re-renders — validation, wire:poll, wire:navigate, a redirect — you need to know whether your island re-mounts, receives new props, or silently keeps stale ones. Test the update path explicitly, not just the initial load. Most "MingleJS is broken" reports are actually "my local ref shadowed the incoming prop."

Only serializable props survive the wire. Closures, Eloquent models, collections with loaded relations, enums without a cast — all of it either throws or surprises you. Map to arrays/IDs/scalars in the Livewire component. If a prop is null in JS, that's the first thing to check.

The Vite glob is the silent failure. div present, no console error, component just… isn't there? Your file isn't in the input glob. Check the built manifest in public/build/ before you start debugging Livewire. Also make sure the component is registered/found by whatever convention the docs specify — a mount error naming the component is a registration problem, not a Livewire problem.

Pin everything and validate the matrix. ijpatricio/mingle, livewire/livewire, and Vue/React versions interact. The package is young with a single primary maintainer and a recent release cadence; check the README/releases for compatibility before bumping any of the three. Never bump Livewire major versions in the same PR as an island change.

Decide which layer is broken first. Two render models means two mental models. Server round-trip, validation, authorization → Livewire. Mount, props, local state, reactivity → island. Conflating them is where hours disappear. A fast discriminator: does the bug survive a hard refresh with a fresh mount? If yes, suspect props/server. If it only appears after an interaction, suspect local state.

Degrade gracefully — non-negotiable. The island must render a useful placeholder (skeleton, static table, plain HTML) while JS loads, and the rest of the page's Livewire behaviour must remain functional if the island's JS fails outright. Ship the Blade fallback first, then the island.

Chunk and defer the heavier islands. A chart library or virtualized table can add hundreds of KB. Keep island JS out of your main bundle (dynamic import() inside the mount path, or separate Vite entries) so the initial page paint isn't hostage to an island below the fold.

Debugging shortcuts that actually work:

  • Inspect the rendered div — serialized props are typically in a data-* attribute or inline script.
  • $wire is available in the console on any Livewire page; you can call actions manually to isolate whether the server side works without touching the JS component.
  • Console errors at mount are almost always prop-shape mismatches or missing registration. Console errors on interaction are almost always your own state management.

Avoid wire:ignore as a reflex. It'll stop the flicker and also stop your prop updates. If you find yourself reaching for it, the real fix is usually to let the island own its local state and only push new server data as props.

Bus factor: treat this as a dependency you own. 445 stars, zero listed dependents, one maintainer, MIT. That's fine for an internal admin panel and a real consideration for anything load-bearing. Practical mitigations: fork it into your org's namespace or vendor it, and write down the removal path (swap the island for Inertia, or downgrade it to Alpine + a plain fetch) so the exit stays a sprint, not a quarter.

Don't over-adopt. The temptation after the first island works is to rewrite the whole frontend. The package's entire value is component-sized, reversible bets. Keep the island count low, the boundaries sharp, and every island independently removable — that's the property that makes this a good decision rather than a slow-motion rewrite.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk