├── .gitignore ├── Examples ├── computed-properties.md ├── entangle.md ├── event-listeners-over-polling.md ├── form-request.md ├── lazy-loading.md ├── loading-states.md ├── nesting-level.md ├── root-element.md ├── route-model-binding.md └── wire-model-modifiers.md ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | -------------------------------------------------------------------------------- /Examples/computed-properties.md: -------------------------------------------------------------------------------- 1 | ### 📦 Use computed properties to access database 2 | 3 | :x: Bad: 4 | ```php 5 | public function countries(): Collection 6 | { 7 | return Country::select('name', 'code') 8 | ->orderBy('name') 9 | ->get(); 10 | } 11 | ``` 12 | 13 | :heavy_check_mark: Good: 14 | ```php 15 | #[Computed] 16 | public function countries(): Collection 17 | { 18 | return Country::select('name', 'code') 19 | ->orderBy('name') 20 | ->get(); 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /Examples/entangle.md: -------------------------------------------------------------------------------- 1 | ### 🔗 Entangle your live data 2 | 3 | ```html 4 |
5 | 6 | 7 |
8 | ``` 9 | -------------------------------------------------------------------------------- /Examples/event-listeners-over-polling.md: -------------------------------------------------------------------------------- 1 | ### ☔ Prefer to use event listeners over polling 2 | 3 | :x: Bad: 4 | ```html 5 |
6 | User Content 7 |
8 | ``` 9 | 10 | :heavy_check_mark: Good: 11 | 12 | *Define the listener in the component using `On` attribute:* 13 | ```php 14 | class Dashboard extends Component 15 | { 16 | #[On('post-created')] 17 | public function updatePostList($title) 18 | { 19 | // ... 20 | } 21 | } 22 | ``` 23 | 24 | *Dispatch the event in every other component:* 25 | ```php 26 | $this->dispatch('post-created'); 27 | ``` 28 | -------------------------------------------------------------------------------- /Examples/form-request.md: -------------------------------------------------------------------------------- 1 | ### 🌎 Use Form Request rules for validation 2 | 3 | :x: Bad: 4 | ```php 5 | public function rules(): array 6 | { 7 | return [ 8 | 'field1' => ['required', 'string'], 9 | 'field2' => ['required', 'integer'], 10 | 'field3' => ['required', 'boolean'], 11 | ]; 12 | } 13 | ``` 14 | 15 | :heavy_check_mark: Good: 16 | ```php 17 | public function rules(): array 18 | { 19 | return (new MyFormRequest)->rules(); 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /Examples/lazy-loading.md: -------------------------------------------------------------------------------- 1 | 📈 Use lazy loading 2 | 3 | Example: 4 | 5 | Add the `placeholder` method in the component class: 6 | ```php 7 | public function placeholder(): string 8 | { 9 | return <<<'HTML' 10 |
11 | 12 | ... 13 |
14 | HTML; 15 | } 16 | ``` 17 | 18 | Add the component with the `lazy` attribute: 19 | ```html 20 | 21 | ``` -------------------------------------------------------------------------------- /Examples/loading-states.md: -------------------------------------------------------------------------------- 1 | ### 💱 Always use loading states for better UX 2 | 3 | Example: 4 | ```html 5 |
6 | 7 | 8 | 9 | Loading... 10 | 11 |
12 | ``` 13 | -------------------------------------------------------------------------------- /Examples/nesting-level.md: -------------------------------------------------------------------------------- 1 | ### 🧵 Keep component nesting level at 1 2 | 3 | Example: 4 | ```html 5 |
6 |

Component

7 | 8 |
9 | ``` 10 | -------------------------------------------------------------------------------- /Examples/root-element.md: -------------------------------------------------------------------------------- 1 | ### 🌳 Always set up root element 2 | 3 | :x: Bad: 4 | ```html 5 |

Component Name

6 |
Content
7 | ``` 8 | 9 | :heavy_check_mark: Good: 10 | ```html 11 |
12 |

Component Name

13 |
Content
14 |
15 | ``` 16 | -------------------------------------------------------------------------------- /Examples/route-model-binding.md: -------------------------------------------------------------------------------- 1 | ### 🗺️ Use Route Model Binding to fetch the model 2 | 3 | Example `mount`: 4 | 5 | ```php 6 | public function mount(User $user): void 7 | { 8 | $this->fill($user); 9 | } 10 | ``` 11 | 12 | :x: Bad: 13 | ```html 14 | 15 | ``` 16 | 17 | :heavy_check_mark: Good: 18 | ```html 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /Examples/wire-model-modifiers.md: -------------------------------------------------------------------------------- 1 | ### 💡 Avoid using *live* wire:model modifier where possible 2 | 3 | :x: Bad: 4 | ```html 5 | 6 | ``` 7 | 8 | :heavy_check_mark: Better: 9 | ```html 10 | 11 | ``` 12 | 13 | :heavy_check_mark: Even better: 14 | ```html 15 | 16 | ``` 17 | 18 | :heavy_check_mark: Ideal: 19 | ```html 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Michael Rubel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Livewire Logo

2 | 3 | ## Livewire Best Practices 4 | [![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) 5 | 6 | This repository is a curated list of general recommendations on how to use [Laravel Livewire framework](https://github.com/livewire/livewire) to meet enterprise concerns regarding security, performance, and maintenance of Livewire components. 7 | 8 | ### Short Introduction 9 | My name is [Michael Rubél](https://github.com/michael-rubel) and I started using the Livewire framework in 2019 when it was new and barely stable. Back in the day, I was impressed with how fast dynamic UIs can be shipped without even using JavaScript. But like any software solution, it had its pitfalls, and I had to deal with them. The main goal of this repository is to collect the most important experiences you need to consider when working with Livewire. 10 | 11 | Let's begin... 12 | 13 | --- 14 | ### 🌳 Always set up the root element 15 | Livewire requires a root element (div) in each component. You should always write code inside `
Your Code Here
`. Omitting this structure will lead to a lot of problems with updating components. 16 | 17 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/root-element.md) 18 | 19 | --- 20 | ### ✨ The Golden rule of performant Livewire 21 | ```html 22 | Don't pass large objects to Livewire components! 23 | ``` 24 | 25 | Avoid passing objects to the component's public properties if possible. Use primitive types: strings, integers, arrays, etc. That's because Livewire serializes/deserializes your component's payload with each request to the server to share the state between the frontend & backend. If you need to work on objects, you can create them inside a method or computed property, and then return the result of the processing. 26 | 27 | What to consider a large object? 28 | - Any instance as large as the Eloquent model is big enough already for Livewire to slow down the component lifecycle, which may lead to poor performance on live updates. For example, if you have a component representing the user profile (email and username), pass these parameters to properties as strings. 29 | 30 | Note: if you use [full-page components](https://livewire.laravel.com/docs/components#full-page-components), it's recommended to fetch objects in the full-page component itself, and then pass them downstairs to the nested ones as primitive types. 31 | 32 | --- 33 | ### 🧵 Keep component nesting level at 1 34 | If you had a Livewire component (0) that includes another Livewire component (1), then you shouldn't nest it deeper (2+). Too much nesting can make a headache when dealing with DOM diffing issues. 35 | 36 | Also, prefer the usage of Blade components when you use nesting, they will be able to communicate with the parent's Livewire component but won't have the overhead the Livewire adds. 37 | 38 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/nesting-level.md) 39 | 40 | --- 41 | ### 📝 Utilize the form objects 42 | Livewire v3 introduced a new abstraction layer called `Form Objects`. Always use them because that makes your components more maintainable in the long run. 43 | 44 | [Docs](https://livewire.laravel.com/docs/forms) 45 | 46 | --- 47 | ### 🕵️ Don't pass sensitive data to the components 48 | Avoid situations that may lead to passing sensitive data to the Livewire components, because it can be easily accessed from the client-side by default. You can hide the properties from the frontend using `#[Locked]` attribute starting from Livewire version 3. 49 | 50 | --- 51 | ### ☔ Prefer to use event listeners over polling 52 | Instead of constantly [polling](https://livewire.laravel.com/docs/polling) the page to refresh your data, you may use [event listeners](https://livewire.laravel.com/docs/events#listening-for-events) to perform the component update only after a specific task was initiated from another component. 53 | 54 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/event-listeners-over-polling.md) 55 | 56 | --- 57 | ### 📦 Use computed properties to access the database 58 | You can use [computed properties](https://livewire.laravel.com/docs/computed-properties) to avoid unnecessary database queries. Computed properties are cached within the component's lifecycle and do not run multiple times in the component class or the blade view. Starting from Livewire v3, the result of computed properties can also be cached in the generic application-level cache (for example Redis), [see](https://livewire.laravel.com/docs/computed-properties#caching-between-requests). 59 | 60 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/computed-properties.md) 61 | 62 | --- 63 | ### 🗺️ Use Route Model Binding to fetch the model 64 | Pass only an ID or UUID to the `mount` method, then map the model attributes to component properties. Remember: don't assign a whole model, but its attributes only. To avoid manually mapping model attributes, you can use the `fill` method. 65 | 66 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/route-model-binding.md) 67 | 68 | --- 69 | ### 💡 Avoid using *live* wire:model modifier where possible 70 | Avoid using `live` wire:model modifier. This dramatically reduces unnecessary requests to the server. 71 | In Livewire version 3, all the models are deferred by default (old: `defer` modifier), which is good. 72 | 73 | [Examples](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/wire-model-modifiers.md) 74 | 75 | --- 76 | ### 👨‍💻 Use Artisan commands to create, move, and rename components 77 | Livewire has [built-in Artisan commands](https://livewire.laravel.com/docs/quickstart#create-a-livewire-component) to create, move, rename components, etc. 78 | For example, instead of manually renaming files, which could be error-prone, you can use the following command: 79 | - `php artisan livewire:move Old/Path/To/Component New/Path/To/Component` 80 | 81 | --- 82 | ### 💱 Always use loading states for better UX 83 | You can use [loading states](https://livewire.laravel.com/docs/loading#basic-usage) to make UX better. It will indicate to the user that something is happening in the background if your process is running longer than expected. To avoid flickering, you can use the `delay` modifier. 84 | 85 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/loading-states.md) 86 | 87 | --- 88 | ### 📈 Use lazy loading 89 | Instead of blocking the page render until your data is ready, you can create a placeholder using the [lazy loading](https://livewire.laravel.com/docs/lazy) technique to make your UI more responsive. 90 | 91 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/lazy-loading.md) 92 | 93 | --- 94 | ### 🔗 Entangle 95 | Sync your data with the backend using [$wire.entangle](https://livewire.laravel.com/docs/alpine#sharing-state-using-wireentangle) directive. This way the model will be updated instantly on the frontend, and the data will persist server-side after the network request reaches the server. It dramatically improves the user experience on slow devices. This approach is called "Optimistic Response" or "Optimistic UI" in other frontend communities. 96 | 97 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/entangle.md) 98 | 99 | --- 100 | ### 🌎 Use Form Request rules for validation 101 | Livewire doesn't support [Form Requests](https://laravel.com/docs/9.x/validation#form-request-validation) internally, but instead of hardcoding the array of validation rules in the component, you may get it directly from Form Request. 102 | This way you can reuse the same validation rules in different application layers, for example in API endpoints. 103 | 104 | [Example](https://github.com/michael-rubel/livewire-best-practices/blob/main/Examples/form-request.md) 105 | 106 | --- 107 | ### 🧪 Always write feature tests 108 | Even simple tests can greatly help when you change something in the component. 109 | Livewire has a straightforward yet powerful [testing API](https://livewire.laravel.com/docs/testing). 110 | 111 | --- 112 | > 🔨 Are you working with Livewire daily?\ 113 | > Suggest your best practices if you don't see them on the list.\ 114 | > If you aren't sure if it's a good practice, you can [start a discussion](https://github.com/michael-rubel/livewire-best-practices/discussions/new). 115 | 116 | ## Contributors 117 | [![GitHub Contributors Image](https://contrib.rocks/image?repo=michael-rubel/livewire-best-practices)](https://github.com/michael-rubel/livewire-best-practices/graphs/contributors) 118 | --------------------------------------------------------------------------------