|
| 1 | +--- |
| 2 | +title: Form Builder - Common Errors to Avoid |
| 3 | +slug: leandrocfe-form-builder-common-errors-to-avoid |
| 4 | +author_slug: leandrocfe |
| 5 | +publish_date: 2024-11-23 |
| 6 | +categories: [form-builder, tailwind-css] |
| 7 | +type_slug: article |
| 8 | +--- |
| 9 | + |
| 10 | +## Introduction |
| 11 | +As a member of the Filament community, I frequently respond to questions and issues raised in [Filament Discord](https://filamentphp.com/discord) and [Filament GitHub discussions](https://github.com/filamentphp/filament/discussions). |
| 12 | +Many of these involve common mistakes that developers make while building forms in Filament. |
| 13 | +In this article, I'll highlight some of these errors and show you how to avoid them. |
| 14 | + |
| 15 | +> **_NOTE:_** This article was written using [Filament v3.x](https://filamentphp.com/docs/3.x). |
| 16 | +
|
| 17 | +## Error 1: Using New Tailwind Classes Without Defining a Theme |
| 18 | + |
| 19 | +Suppose you want to add a custom Tailwind class to the [helperText](https://filamentphp.com/docs/3.x/forms/fields/getting-started#adding-helper-text-below-the-field) of a [RichEditor](https://filamentphp.com/docs/3.x/forms/fields/rich-editor) component: |
| 20 | + |
| 21 | +```php |
| 22 | +RichEditor::make('content') |
| 23 | + ->columnSpanFull() |
| 24 | + ->helperText(new HtmlString(<<<'HTML' |
| 25 | + <div class="text-gray-700 dark:text-white py-4"> |
| 26 | + You can use |
| 27 | + <a |
| 28 | + href="https://commonmark.org/help/" |
| 29 | + target="_blank" |
| 30 | + class="text-blue-500 font-bold hover:underline" |
| 31 | + > |
| 32 | + Markdown |
| 33 | + </a> to format your content. |
| 34 | + </div> |
| 35 | + HTML) |
| 36 | + ), |
| 37 | +``` |
| 38 | + |
| 39 | +If you run this code without creating a custom theme, the new class **text-blue-500** will not be applied: |
| 40 | + |
| 41 | + |
| 42 | +### Why? |
| 43 | +Tailwind CSS compiles only classes explicitly referenced in scanned files. Classes dynamically added in your Blade files will be ignored unless you configure Tailwind to scan those files. |
| 44 | + |
| 45 | +### Solution: |
| 46 | +- Create a [custom theme](https://filamentphp.com/docs/3.x/panels/themes#creating-a-custom-theme). |
| 47 | +- Follow the instructions in the command: |
| 48 | + - _First, add a new item to the `input` array of `vite.config.js`: `resources/css/filament/admin/theme.css`_ |
| 49 | + - _Next, register the theme in the admin panel provider using `->viteTheme('resources/css/filament/admin/theme.css')`_ |
| 50 | + |
| 51 | +- Update the content array in your `tailwind.config.js` to include the relevant directory: |
| 52 | +```js |
| 53 | +export default { |
| 54 | + presets: [preset], |
| 55 | + content: [ |
| 56 | + './app/Filament/**/*.php', |
| 57 | + './resources/views/filament/**/*.blade.php', |
| 58 | + './vendor/filament/**/*.blade.php', |
| 59 | + //... |
| 60 | + ], |
| 61 | +} |
| 62 | +``` |
| 63 | +- Finally, run `npm run dev` or `npm run build` to compile the theme. |
| 64 | + |
| 65 | +After following these steps, the custom class will be applied successfully: |
| 66 | + |
| 67 | + |
| 68 | + |
| 69 | +## Error 2: Misusing the `default()` Method in Form Components |
| 70 | + |
| 71 | +Here's an example of a [TagsInput](https://filamentphp.com/docs/3.x/forms/fields/tags-input) component with default tags in a PostResource: |
| 72 | + |
| 73 | +```php |
| 74 | +TagsInput::make('tags') |
| 75 | + ->default([ |
| 76 | + 'Laravel', |
| 77 | + 'Livewire', |
| 78 | + 'Filament', |
| 79 | + ]), |
| 80 | +``` |
| 81 | + |
| 82 | +This works perfectly on a Create Page: |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +However, if you edit a post with no tags, the defaults won't apply: |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +### Why? |
| 91 | +As the [docs say](https://filamentphp.com/docs/3.x/forms/fields/getting-started#setting-a-default-value), _defaults are only used when the form is loaded without existing data. Inside panel resources this only works on Create Pages, as Edit Pages will always fill the data from the model._ |
| 92 | + |
| 93 | +In this case, the value is `null`, which is correct on the Edit Page. |
| 94 | + |
| 95 | +If you want to force the input to have a default value on the Edit Page if the value is `null`, you should use the `formatStateUsing()` method: |
| 96 | + |
| 97 | +```php |
| 98 | +TagsInput::make('tags') |
| 99 | + ->formatStateUsing(fn (?array $state): array => blank($state) ? [ |
| 100 | + 'Laravel', |
| 101 | + 'Livewire', |
| 102 | + 'Filament', |
| 103 | + ] : $state), |
| 104 | +``` |
| 105 | + |
| 106 | +## Error 3: Combining `options()` and `relationship()` Methods in Select Components |
| 107 | + |
| 108 | +When using a [Select](https://filamentphp.com/docs/3.x/forms/fields/select) or [CheckboxList](https://filamentphp.com/docs/3.x/forms/fields/checkbox-list) component with a `relationship()`, avoid also defining `options()`. For instance: |
| 109 | + |
| 110 | +```php |
| 111 | +Select::make('categories') |
| 112 | + ->multiple() |
| 113 | + ->preload() |
| 114 | + ->relationship( |
| 115 | + name: 'categories', |
| 116 | + titleAttribute: 'name' |
| 117 | + ) |
| 118 | + ->options(Category::wherePublished(true)->pluck('name', 'id')), // Don't use this |
| 119 | +``` |
| 120 | +### Why? |
| 121 | +The `relationship` method already fetches `options` from the database using relationship methods in your Eloquent models. |
| 122 | + |
| 123 | +### Solution: |
| 124 | +Use the [modifyQueryUsing()](https://filamentphp.com/docs/3.x/forms/fields/select#customizing-the-relationship-query) method to customize the query: |
| 125 | + |
| 126 | +```php |
| 127 | +Select::make('categories') |
| 128 | + ->multiple() |
| 129 | + ->preload() |
| 130 | + ->relationship( |
| 131 | + name: 'categories', |
| 132 | + titleAttribute: 'name', |
| 133 | + modifyQueryUsing: fn (Builder $query): Builder => $query->wherePublished(true)), |
| 134 | +``` |
| 135 | + |
| 136 | +## Error 4: Incorrectly Using a Wizard Component in Resource Pages |
| 137 | +Using a [Wizard](https://filamentphp.com/docs/3.x/forms/layout/wizard) component directly in a Resource can result in both navigation and form buttons appearing simultaneously: |
| 138 | + |
| 139 | + |
| 140 | + |
| 141 | +### Why? |
| 142 | +The [Wizard](https://filamentphp.com/docs/3.x/forms/layout/wizard) component has its own navigation buttons. |
| 143 | + |
| 144 | +### Solution: |
| 145 | +Use the `HasWizard` trait in your Resource Pages: |
| 146 | + |
| 147 | +Create Page: |
| 148 | +```php |
| 149 | +use App\Filament\Resources\CategoryResource; |
| 150 | +use Filament\Resources\Pages\CreateRecord; |
| 151 | + |
| 152 | +class CreateCategory extends CreateRecord |
| 153 | +{ |
| 154 | + use CreateRecord\Concerns\HasWizard; |
| 155 | + |
| 156 | + protected static string $resource = CategoryResource::class; |
| 157 | + |
| 158 | + protected function getSteps(): array |
| 159 | + { |
| 160 | + return [ |
| 161 | + // ... |
| 162 | + ]; |
| 163 | + } |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +Edit Page: |
| 168 | +```php |
| 169 | +use App\Filament\Resources\CategoryResource; |
| 170 | +use Filament\Resources\Pages\EditRecord; |
| 171 | + |
| 172 | +class EditCategory extends EditRecord |
| 173 | +{ |
| 174 | + use EditRecord\Concerns\HasWizard; |
| 175 | + |
| 176 | + protected static string $resource = CategoryResource::class; |
| 177 | + |
| 178 | + protected function getSteps(): array |
| 179 | + { |
| 180 | + return [ |
| 181 | + // ... |
| 182 | + ]; |
| 183 | + } |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +To implement a [Wizard within an Action](https://filamentphp.com/docs/3.x/actions/prebuilt-actions/create#using-a-wizard), use the `steps()` method: |
| 188 | + |
| 189 | +```php |
| 190 | +CreateAction::make() |
| 191 | + ->steps([ |
| 192 | + // ... |
| 193 | + ]), |
| 194 | +``` |
| 195 | + |
| 196 | +This ensures proper functionality by displaying only the navigation buttons. |
| 197 | + |
| 198 | +## Error 5: Forgetting Key Steps in Standalone Mode |
| 199 | + |
| 200 | +Filament provides a [Standalone Mode](https://filamentphp.com/docs/3.x/forms/adding-a-form-to-a-livewire-component) to build forms. |
| 201 | +When using Filament's Standalone Mode in a Livewire component, missing [key steps](https://filamentphp.com/docs/3.x/forms/adding-a-form-to-a-livewire-component#adding-the-form) can cause unexpected behavior. |
| 202 | +For example: |
| 203 | + |
| 204 | +```php |
| 205 | +class CreateCategory extends Component implements HasForms |
| 206 | +{ |
| 207 | + use InteractsWithForms; |
| 208 | + |
| 209 | + public ?array $data = []; |
| 210 | + |
| 211 | + public function mount(): void |
| 212 | + { |
| 213 | + $this->form->fill(); // Important for initializing the form |
| 214 | + } |
| 215 | + |
| 216 | + public function form(Form $form): Form |
| 217 | + { |
| 218 | + return $form |
| 219 | + ->schema([ |
| 220 | + TextInput::make('name'), |
| 221 | + TextInput::make('slug'), |
| 222 | + ... |
| 223 | + ]) |
| 224 | + ->statePath('data'); // Important for storing form data |
| 225 | + } |
| 226 | + ... |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +### Solution: |
| 231 | +Ensure you are following these [key steps](https://filamentphp.com/docs/3.x/forms/adding-a-form-to-a-livewire-component#adding-the-form) when using **Standalone Mode**. |
| 232 | + |
| 233 | +If you omit `statePath()`, ensure that [public properties](https://livewire.laravel.com/docs/properties) exist for each [Form Field](https://filamentphp.com/docs/3.x/forms/fields/getting-started): |
| 234 | + |
| 235 | +```php |
| 236 | +class CreateCategory extends Component implements HasForms |
| 237 | +{ |
| 238 | + use InteractsWithForms; |
| 239 | + |
| 240 | + // Add public properties for each field in the form schema |
| 241 | + public ?string $name = null; |
| 242 | + public ?string $slug = null; |
| 243 | + ... |
| 244 | + |
| 245 | + public function mount(): void |
| 246 | + { |
| 247 | + $this->form->fill(); |
| 248 | + } |
| 249 | + public function form(Form $form): Form |
| 250 | + { |
| 251 | + return $form |
| 252 | + ->schema([ |
| 253 | + TextInput::make('name'), |
| 254 | + TextInput::make('slug'), |
| 255 | + ... |
| 256 | + ]); |
| 257 | + } |
| 258 | + ... |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +## Conclusion |
| 263 | +Avoiding these common errors will save you time and provide a smoother development experience with Filament. |
| 264 | +Additionally, it will deepen your understanding of Filament's features, enabling you to create more maintainable and reliable forms for your projects. |
| 265 | + |
| 266 | +Happy coding ;) |
0 commit comments