vincent15000's avatar

Numeric value like 2.

Hello,

It's in a Livewire component, but Livewire has probably nothing to do with this issue.

When I type in the input number field :

  • 2 => it's a numeric value

  • 2. => it's not a numeric value

  • 2.0 => it's a numeric value

  • 2, => it's a numeric value

Checked with is_numeric($this->value).

I need all values to be numeric values and I want that the user can type a point to separate integers and decimals.

The problem is : if the user types 2. and not 2.0, I need that this value is casted as a numeric value.

How to force this ?

I wonder if the best solution is not to simply catch each key press and replace each . immediately with a ,. I have tried to prevent default if @keydown.period (with Alpine) and concatenate a comma, but it doesn't work, it seems to be more complex than I thought.

@keydown.period="
    $event.preventDefault();
    $el.value = $event.target.value+','; // this replaces 2. by an empty string as soon as I type the dot
"

So I don't know how to solve this : handle the issue in PHP or intercept the . ? And for both ways, how to do ? I'm searching for hours without any success.

Thanks for your help.

V

0 likes
20 replies
martinbean's avatar

@vincent15000 What is the actual problem you’re trying to solve here? If you’re using Livewire, why aren‘t you using the wire:model directive to bind the input value to a Livewire component property?

class Foo extends Component
{
    public $amount;
}
<input name="amount" type="number" wire:model="amount">
1 like
vincent15000's avatar

@martinbean I already do that, but the problem is : when I type the dot after a number, the input value is reset to an empty string.

public function updatedVariables()
{
    foreach ($this->variables as $index => $variable) {
		dump($variable->value); // empty string if I type the dot
    }

    $this->calculateQuantities();
}
martinbean's avatar

@vincent15000 Then I’d use a computed property to separate the input value, from the value you actually want to perform calculations on.

class Foo extends Component
{
    public string $quantity;

    #[Computed]
    public function sanitizedQuantity(): int
    {
        return (int) $this->quantity;
    }
}

Also use a type="number" input in your form. This will stop people typing non-numeric characters:

<input
    name="quantity"
    type="number"
    wire:model="quantity"
>

If you want users to be able to type decimal amounts, then you can use the step attribute:

  <input
      name="quantity"
+     step="0.01"
      type="number"
      wire:model="quantity"
  >
Tray2's avatar

I would just add code to trim any excess non numeric values.

1 like
vincent15000's avatar

@Tray2

I already tried to do that.

public function updatedVariables()
{
    foreach ($this->variables as $index => $variable) {
        if (!$variable->value || Str::trim($variable->value) === '') {
            $this->variables[$index]->value = null;
        }
    }

    $this->calculateQuantities();
}

But after trim, 2. is an empty string.

Very strange ...

vincent15000's avatar

@jsanwo64 I don't want to trim, I want to keep the dot as if it were a comma. The input field has the type number.

Jsanwo64's avatar

@vincent15000 try

public function updatedVariables()
{
    foreach ($this->variables as $index => $variable) {
        $value = Str::trim($variable->value);

        if ($value === '' || $value === null) {
            $this->variables[$index]->value = null;
            continue;
        }

        // Normalize commas to dots
        $value = str_replace(',', '.', $value);

        // Case: "2." → force "2.0"
        if (preg_match('/^\d+\.$/', $value)) {
            $value .= '0';
        }

        // Final check
        if (is_numeric($value)) {
            $this->variables[$index]->value = $value + 0; // cast to int/float
        } else {
            $this->variables[$index]->value = null;
        }
    }

    $this->calculateQuantities();
}
Jsanwo64's avatar

@vincent15000 what type is your input field?

If you must support 2., then you cannot keep type="number.

You need type="text" inputmode="decimal" to let users type 2. freely.

<input 
    type="text"
    inputmode="decimal"
    wire:model.lazy="variables.{{ $index }}.value"
    pattern="[0-9]+([.,][0-9]*)?"
>

Then normalize in PHP

public function updatedVariables()
{
    foreach ($this->variables as $index => $variable) {
        $value = Str::trim($variable->value);

        if ($value === '' || $value === null) {
            $this->variables[$index]->value = null;
            continue;
        }

        // Normalize commas to dots
        $value = str_replace(',', '.', $value);

        // Case: "2." → force "2.0"
        if (preg_match('/^\d+\.$/', $value)) {
            $value .= '0';
        }

        // Final check
        if (is_numeric($value)) {
            $this->variables[$index]->value = $value + 0; // cast to int/float
        } else {
            $this->variables[$index]->value = null;
        }
    }

    $this->calculateQuantities();
}

I hope you understand?

Snapey's avatar

Do you use number field for input? If not why not?

Beware though. Numeric input allows e in number fields.

locale also plays a part when it comes to periods, commas and spaces

all valid numbers in different parts of europe

  • 1,234.56
  • 1234,56
  • 1 234,56
1 like
vincent15000's avatar

@Snapey I use a number field for the input.

Hmmm yes sure ... is there a solution with localization ?

JussiMannisto's avatar

@vincent15000 The solution is to rely on browser/OS localization, and not try to force it yourself. Even if a number is entered in some localized format (1 234,56), it's still submitted to the server in a normalized format (1234.56).

1 like
vincent15000's avatar

@JussiMannisto Yes but it doesnt answer my question : 2. is rejected and automatically replaced by an empty string.

I just want that 2. remains in the input field if typed in and correctly casted to a number (currently it isn't).

JussiMannisto's avatar

@vincent15000 It's not a valid number, so native methods will not help you there.

If you want the field to accept specific formats, you can make the type attribute text instead of number, and provide your own regex in the pattern attribute. You can then do what you want in the value in PHP.

Can I ask why you want 2. to be accepted as a number, when it's not considered one by browsers or PHP?

1 like
vincent15000's avatar

@JussiMannisto Just because when I type 2.5, I type 2 then . and the content is replaced by an empty string before I have time enough to type 5.

Sure I could for example add a debounce, but I wanted to find another solution.

LaryAI's avatar
Level 58

You’re correct that is_numeric('2.') returns false in PHP, because 2. (with a trailing dot and no decimals) is not considered a valid numeric string. However, is_numeric('2,') returns true in some locales, but this is not reliable and can lead to inconsistent behavior.

Best Solution:
You should normalize the input before validation. If the user enters 2., you can safely treat it as 2.0 (or just 2). This can be handled in PHP, and you don’t need to mess with JavaScript for this.

PHP Solution

You can use a mutator or a custom method in your Livewire component to normalize the value before validation or usage:

public function updatedValue($value)
{
    // Replace comma with dot for decimal separator (optional, based on your locale)
    $value = str_replace(',', '.', $value);

    // If value ends with a dot (e.g. "2."), append a zero
    if (preg_match('/^\d+\.$/', $value)) {
        $value .= '0';
    }

    // Now you can safely check if it's numeric
    if (is_numeric($value)) {
        $this->value = $value;
    } else {
        // Handle invalid input
        $this->value = null;
    }
}

Usage:
Bind your input to wire:model="value" and Livewire will automatically call updatedValue() when the input changes.

Explanation

  • This code replaces commas with dots (if needed).
  • If the value ends with a dot (like 2.), it appends a zero to make it 2.0.
  • Now is_numeric() will return true for 2.0.

Optional: JavaScript/Alpine Solution

If you want to handle this on the frontend with Alpine.js, you can do something similar:

<input
    type="text"
    x-data
    @input="
        if ($el.value.match(/^\d+\.$/)) {
            $el.value += '0';
        }
    "
    wire:model="value"
/>

But it’s usually better to handle this normalization in PHP, as shown above, to ensure consistency regardless of how the value is set.


Summary:
Normalize the value in PHP by appending a zero if the value ends with a dot. This way, 2. becomes 2.0 and passes is_numeric().

Let me know if you need a more detailed example!

Please or to participate in this conversation.