earmsby's avatar

Calculated value on a form field

I have a form with a bunch of fields. I'm trying to create a calculated field (read only) that does some math on the other fields. At first I tried to use:

                        Placeholder::make('suggested_price')
                            ->label('Suggested Price')
                            ->content(function (Get $get) {
                                return '$' . $get('page_count') * .002;
                            }),

This actually works but apparently "Placeholder" is deprecated in Filament 4.0. Through a little searching it seemed like the accepted practice to accomplish this is to use TextInput with ->state. So then I tried this:

                        TextInput::make('suggested_price')
                            ->label('Suggested price')
                            ->disabled()
                            ->state(function (Get $get) {
                                return '$' . $get('page_count') * .002;
                            })

But when I use TextInput as above I get an error:

Typed property Filament\Schemas\Components\Component::$container must not be accessed before initialization

Should I just use Placeholder since it's working? If not, what do I need to do/add in order to resolve the error?

0 likes
5 replies
earmsby's avatar

Sorry for not adding the whole schema - there were a lot of fields. Here's what I have:

    public static function configure(Schema $schema): Schema
    {
        return $schema
            ->components([
                  Section::make('Physical Details')
                    	->icon(Heroicon::BookOpen)
                    	->columnSpanFull()
                    	->columns(4)
                    	->collapsible()
                   		 ->schema([
                        	[... other fields not relevant to this ...]
                        	TextInput::make('page_count')
                            	->columnSpan(2)
                            	->live(onBlur: true)
                            	->numeric(),
							[... other fields not relevant to this ...]

                        	TextInput::make('suggested_price_2')
                            	->label('Suggested price (2)')
                            	->disabled()
                            	->state(function (Get $get) {
                                	return filled($get('page_count')) ? '$' . $get('page_count') * .002 : null;
                            	})
                    ]),

I added the filled(...) function per your suggestion but unfortunately I still get the same error.

Again, using

Placeholder::make('suggested_price_1')
           ->label('Suggested Price (1)')
           ->content(function (Get $get) {
                 return '$' . $get('page_count') * .002;
           }),

doesn't throw an error so it seemed to me that $get('page_count') wasn't the cause of the problem. If the field has not been filled in yet, the Placeholder method just returns "$ 0"

LaryAI's avatar
Level 58

You're correct that in Filament v4, Placeholder is deprecated and the recommended way is to use other components like TextInput or a Field::make() variant. However, the issue arises from trying to use the ->state() method for a computed, display-only value. The state callback is supposed to return a directly editable value for the field, not a computed value based on other states.

Here’s how you can achieve your requirement (a read-only, calculated field) in Filament v4:

1. Use TextInput with ->extraAttributes(['readonly' => true]) and ->dehydrated(false)

You can use the afterStateUpdated/live methods to update the value dynamically. But, for cases like yours (especially if you don't want the field to be saved), you'll want to avoid using state and instead use formatStateUsing.

Example:

TextInput::make('suggested_price')
    ->label('Suggested price')
    ->default('$0.00')
    ->formatStateUsing(function(Get $get) {
        $count = (int) $get('page_count') ?? 0;
        $price = $count * 0.002;
        return '$' . number_format($price, 2);
    })
    ->extraAttributes(['readonly' => true])
    ->dehydrated(false)
    ->required(false)

Key Points:

  • formatStateUsing lets you compute what shows in the field.
  • extraAttributes(['readonly' => true]) makes it non-editable.
  • dehydrated(false) ensures that it won't be saved to the database (unless you want it).
  • You can remove ->disabled() here unless you really need it to look "disabled". "readonly" still lets users select the value to copy.

2. Alternative: ViewField (if you want even less interactivity)

If you don't need the value to look like an input, but simply want it to display as text, you can use a view component and simply show the computed value. But for your use case, the above approach is appropriate.

3. Why your code fails

The error:

Typed property Filament\Schemas\Components\Component::$container must not be accessed before initialization

...happens because state is used to supply the field’s value for binding (and is used during record hydration), not for simple computed display values—you should use formatStateUsing for display-only/computed values.


Summary

  • Don't use state() for computed display values in Filament v4.
  • Use formatStateUsing and make the field readonly and dehydrated(false) for a calculated, read-only field.

Let me know if you need a more detailed example or have further questions about chaining Filament form fields!

earmsby's avatar

So, Larry AI's suggestion solved the error issue. But, unlike the placeholder example, it does not update when the value of page_count is changed. Back to the drawing board...

earmsby's avatar

Ok, I found the solution to responding to changes in the page_count field. I had to add:

->afterStateUpdated(function (Set $set, Get $get, $state) {
    $price = $state * 0.002;
    $set('suggested_price_2', '$' . number_format($price, 2));
})

to the page_count field.

1 like

Please or to participate in this conversation.