Installation
composer require ethanbarlo/mesh
Publish the config (if needed):
php artisan vendor:publish --provider="EthanBarlo\Mesh\MeshServiceProvider"
Basic Usage
Counter.php):
namespace App\Http\Livewire;
use EthanBarlo\Mesh\MeshComponent;
use Livewire\Component;
class Counter extends Component
{
public $count = 0;
public function increment()
{
$this->count++;
}
public function render()
{
return MeshComponent::render('Counter', [
'count' => $this->count,
'increment' => $this->increment,
]);
}
}
React Component
Create a React component in resources/js/components/Counter.jsx:
import { useState } from 'react';
export default function Counter({ count, increment }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
Mount the Component In your Blade view:
@livewire('counter')
Replace a traditional Livewire component with a React frontend while retaining Livewire’s reactivity. For example, a form with validation:
$errors, $submitted).State Management
$formData, $errors) to React via MeshComponent::render().return MeshComponent::render('UserForm', [
'formData' => $this->formData,
'errors' => $this->errors,
'submit' => $this->submitForm,
]);
Event Handling
onClick={submit}).wire:ignore on the Livewire component’s root element to prevent double-rendering:
@livewire('user-form', key('user-form'))
<div wire:ignore>
{{ $slot }}
</div>
Dynamic Data Fetching
updated() or mount() and pass it to React:
public function mount()
{
$this->extraData = Api::fetchData();
}
public function render()
{
return MeshComponent::render('Dashboard', [
'extraData' => $this->extraData,
]);
}
Reactivity
updated() to trigger React re-renders when data changes:
public function updatedCount()
{
$this->dispatch('count-updated', count: $this->count);
}
window.addEventListener('count-updated', ...).Vite/Laravel Mix
Ensure your React components are compiled and available globally. Example vite.config.js:
export default {
build: {
outDir: 'public/js',
emptyOutDir: true,
},
};
TypeScript Support
Add type definitions for Mesh props in resources/js/types/mesh.d.ts:
declare module '*.mesh' {
import { ComponentType } from 'react';
const MeshComponent: ComponentType<{ count: number; increment: () => void }>;
export default MeshComponent;
}
Testing
test() helper to simulate user interactions:
$this->call('increment')->assertHasNoErrors();
Fallback for Non-JS Users Provide a server-side fallback in Blade:
@livewire('counter')
@if(!app()->isJson())
<div wire:ignore>
<p>Count: {{ $count }}</p>
<button wire:click="increment">Increment</button>
</div>
@endif
Double Rendering
wire:ignore on the Livewire component’s root element and ensure React mounts inside it.State Synchronization
updated() to push changes to React or vice versa via events.CSRF Token Mismatch
@csrf directive in Blade.Hot Module Replacement (HMR)
@livewire('counter', key('counter-' . time()))
Check Mesh Logs
Enable debug mode in config/mesh.php:
'debug' => env('MESH_DEBUG', false),
Logs will appear in Laravel’s log channel.
Inspect Props
Use console.log in React to verify props received from Livewire:
console.log('Props:', props);
Network Tab
Component Mapping
Counter → Counter.jsx).config/mesh.php:
'component_map' => [
'App\Http\Livewire\Counter' => 'react-components/CustomCounter',
],
Asset Versioning
config/mesh.php if needed:
'asset_versioning' => false,
Custom Renderers
Extend MeshComponent to support other frameworks (e.g., Vue, Svelte):
namespace EthanBarlo\Mesh;
class MeshComponent extends \Livewire\Component
{
public static function render(string $componentName, array $props, string $framework = 'react')
{
// Custom logic for Vue/Svelte
}
}
Middleware
Add middleware to Mesh requests in AppServiceProvider:
Mesh::middleware(function ($request, $next) {
if ($request->wireable) {
// Custom logic
}
return $next($request);
});
Event Listeners
Listen for Mesh-specific events (e.g., mesh.rendering):
Mesh::listen('mesh.rendering', function ($component) {
// Log or modify component before rendering
});
How can I help you explore Laravel packages today?