Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jasonfrye's avatar

Alpine x-model on radio buttons issue

I'm trying to capture the total on a registration form for events. These have ad hoc questions created by app users, options that can accumulate costs for each. Some are quantity fields, others checkboxes, and I have those working to update my data total on the main form, but I'm having trouble with the radio buttons.

The radio options on one question might have 3 options, with different prices for each. I've used x-model and the following watch to capture when the selection changes.

<div class="form-group {{$wrapperClass}}"
     x-data="{model{{$field->id}}: null}"
     x-init="$watch('model{{$field->id}}', value => calculateTotal(value))"
>

And that works. But if in the function, how do I know if they have chosen a different radio option after updating my global total amount? Here's the calculateTotal function.

        function calculateTotal(value){
            //look at value, if set and remove that amount from total
            //then add new selection to total
            console.log(value)
        }

How can I see the selected radio button, store that, and then subtract it from the global total if the user decides to choose a different option, especially knowing there may be multiple questions on the form with the different radio options and cost values on each.

0 likes
2 replies
vincent15000's avatar

Where are your radio buttons ? Can you share a complete explanation, so that we can better help you ? ;)

jasonfrye's avatar

@vincent15000 Hey, good point. The radio buttons were just x-model to the wrapper x-data as shown above, but I've refactored the whole thing and have it working now.

Here's what I did. I'm no expert with Alpine, so this might not be best practice. Any input or guidance would be welcome.

    <form
        class="form-ext"
        x-data="formTotalApp"
    >
let formTotalApp = () => {
            return {
                total: 0,
                fees: [],

                add(field, fee) {
                    // Alpine uses a proxy on the arrays,
                    // so we need to turn it back into an array of objects
                    let currentFees = JSON.parse(JSON.stringify(this.fees));

                    // Looks for the field on the array
                    let result = currentFees.find(item => item.id === field);

                    if(result){
                        let newFees = currentFees.map(obj => {
                            if (obj.id === field) {
                                return {...obj, fee: fee};
                            }

                            return obj;
                        });

                        this.fees = newFees;
                    } else {
                        this.fees.push({ id: field, fee: fee });
                    }

                    let totalFees = 0;
                    let sum = this.fees.reduce((totalFees, object) => {
                        return totalFees + parseFloat(object.fee);
                    }, 0);

                    this.total = sum.toFixed(2);
                }
            };
        }

And then on every field that has a cost associated with it, I call the add function, passing it the field ID along with the fee. The add function updates all the values and it stays reactive. Ran into trouble when I realized the Alpine value for an array of objects gets returned as a Proxy, but got past that and it works.

Here's the add function on the radio field. The other fields, like checkbox and number types looks similar.

        <input type="radio"
           id="formOption{{$option->id}}"
           name="field_{{$field->id}}[]"
           class="custom-control-input"
           @if($option->cost) x-on:change="add({{$field->id}}, {{$option->cost}})" @endif
           @if($option->maxAllowedReached()) disabled @endif
           value = "{{$option->id}}"
        >

Please or to participate in this conversation.