Fields being cleared out due to $set call on other fields
This is a bit hard to explain. I have a repeater with two fields, height and width. The repeater is live as are the 2 TextInputs. Then I have a placeholder which does a bunch of calculations.
Placeholder::make('')
->content(function ($get, $set) {
$total = static::newCalcTotal($set, $get);
return new HtmlString("<p></p><br><h1><b>TOTAL $ $total</b></h1>");
})
newCalcTotal
does a bunch of gets, calculates a total for an estimate and sets a few variables. @Dan Harrin gave me some brilliant guidance on this, doing all the calculations in one place and it's working great. Except for this weird issue.
It does NOT touch the height and width fields. Yet clearly, after the calculation, often times the field that has focus (height or width) gets reset to the previous value after the calculation is complete. I validated this by removing the newCalcTotal method call and the problem goes away. One more debug point, which is weird, if I remove the set calls within newCalcTotal the problem also goes away. The $sets I use are unrelated to height/width:
$set('calculations', $calcString);
$set('grandTotal', sprintf("%01.2f", $grand ));
$set('glass_total', $glassTotal);
$set('hardware_total', $hwTotal);
$set('labor_total', $laborTotal);
$set('energy_total', $energy);
$set('tax_amount', $tax);
$set('grand_total', $grand);
I need these sets as they are the result of all the calculations and are stored as part of the record. But if I comment out these $sets, the problem goes away.
I've NO idea how to debug this. It's clearly related to $set although I am NOT setting height or width.31 Replies
Debounce on the fields seem to be related. With no debounce the problem is very repeatable - a fast typist will often lose the second number when typing a 2-digit width. On the other end of the spectrum if I debounce for 2 secs, the same issue happens after the 2 secs.
TextInput::make('width')->required()->numeric()->live()->columnSpan(1),
TextInput::make('height')->required()->numeric()->live()->columnSpan(1),
Nothing weird here.Try ->live(onBlur: true)
That way the request has time to calculate and come back from the server.
I'll try that, thanks but bad news - I commented out the calculation entirely and it STILL happens. I feel like these repeater fields are misbehaving 😩
Yea. Something weird is going on then. But set shouldn’t be affecting other fields. It sounds like something else is going on. I’m just not sure exactly what based on what I’ve seen.
Yeah I no longer think it's set. But these fields are just clearing themselves a lot as you type through them quickly.
Yea. That makes sense with live. When the request comes back the dom diff will cause it to lose anything in the field that was input before the response comes back, that’s why onBlur is usually better. But there’s no much you can do about that since it depends on the latency of the request and the server.
If you have a fast enough server and network, then you probably wouldn’t notice it.
Ugh, I think it's the static part of the call.
$total = static::newCalcTotal($set, $get);
But I'm not sure how else to call it from with the Placeholder. I guess I can try a gigantic closure.
I believe this because when I replace that call with a string, it goes away. But I guess that can still be latency.Can you share the whole thing in a gist?
I'll try but it's soooooo much code. I don't mind the latecny of the calucaltion at all. What I don't get is why would the field that kicked it off get rehydrated (?) after it's done.
I wouldn’t think the static would matter here.
There’s a difference in rehydration and dom diffing. That’s kinda why I want to see everything to make sure it’s not a side effect that might be coming from something else.
Yeah this is a tough one. I'm trying to think how to distill it down. When I made the same repeater elsewhere and kept it simple, no issue. Maybe I'll make a call with a sleep to simulate a latency issue. But I'm not entirely sure that's what's happening. All I can say with certainty is that the field changes after you're done typing. Like frequently. It seems to revert to the prior state.
Definitely sounds like latency to me. Can you at least share a video of the issue with the network tab open in dev tools so we can see the requests.
Sure thing! Didn't realize I could upload a video LOL... Filament is the ONLY place I use Discord. 😉
Mostly interested in seeing the timings of the requests as you type.
In this example it does not happen on the first panel, but does on the second one
In other debug sessions I've seen it revert to the prior field value. The only reason this example is blank is because the prior field value was null.
It appears to NEVER happen on the 1st panel.
Hmm. The request /response is tight in that example. Hard to say. Didn’t realize height and width were in a repeater, would definitely need to see the full code to help further.
Happy to share more code but I know this is asking a lot so I'll try to give the key pieces. Even just giving me things to consider would be helpful.
``php
Section::make('Panels')
->columns(6)
->collapsible()
->schema([
Repeater::make('panels')
->relationship('panels')
->grid(6)
// ->visibleOn('create')
->live()
->reorderable(false)
->cloneable()
->schema([
TextInput::make('width')->required()->numeric()->live(onBlur: true)->columnSpan(1),
TextInput::make('height')->required()->numeric()->live(onBlur: true)->columnSpan(1),
])->columnSpan(6),
]),
Placeholder::make('')
->content(function ($set, $get) {
$total = static::newCalcTotal($set, $get);
return new HtmlString("<p></p><br><h1><b>TOTAL $ $total</b></h1>");
})
public static function newCalcTotal($set, $get)
{
// lots of calculations and ends with:
// AND if these are commented out the issue absolutely goes away
$set('calculations', $calcString);
$set('grandTotal', sprintf("%01.2f", $grand ));
$set('glass_total', $glassTotal);
$set('hardware_total', $hwTotal);
$set('labor_total', $laborTotal);
$set('energy_total', $energy);
$set('tax_amount', $tax);
$set('grand_total', $grand);
return $grand;
}
Ok, but where are all these fields being defined where you are using $set
Where is ‘the calculations, labor_total field, etc.
They're all just hidden fields at the root level
Hidden::make('glass_total'),
Hidden::make('hardware_total'),
Hidden::make('labor_total'),
Hidden::make('energy_total'),
Hidden::make('grand_total'),
Hidden::make('tax_amount'),
Just because you feel something isn’t relevant doesn’t mean it isn’t.
Ok. I’m willing to help here. But you need to stop and take a step back. Since this all looks like things that can affect business logic and the business itself, hidden fields are not going to be the way to go since they can be modified maliciously from the front end.
THAT doesn't sound good. Wasn't aware of that. Yikes.
I feel like you might be over complicating what you are trying to accomplish.
Again, this is just based on what I’m seeing. So I could be wrong, but my gut is telling me that you need to reevaluate your approach right now.
Wouldn't surprise me and I wouldn't be offended. LOL. What I can say is this - there are TONS of fields and calculations and they're all live. It's been fun. But I don't know of another way. Every field impacts the total and so I have all the calcualtions in one place. Just a bunch of gets and the sets I showed. Not sure how else it can be done?
The best approach here would be a method for the calculations, that doesn’t set any hidden fields and the doing the calculation again the save/create/edit actually gets persisted to the record. You can offload the calculation to a service class if you’re worried about duplication.
There’s no reason to worry about $set since the total is the only thing the placeholder is concerned with. So, you can use a dedicated class the takes in the widths and height and just returns a total.
And since it only needs those inputs the same class can have a method that returns the individual fields as an array that can be used when saving the form.
Thus not depending on the front end form state hidden fields keep you safe from data injection.
I do have one method for the calculations.
But the idea of a refactor to a service class and calculating at save/create/edit is intriguing. Thanks for the tip about danger of the hidden fields.
Still don't understand though how it's impacting a repeater field. But I know you can't figure that out with small peaks at the code. This is a tough one for sure. I appreciate you going down the rabbit hole with me though.
No worries. If you decide to share it all in a gist, I’d be happy to explore further.
It would be HUGE. I'd feel bad about the amount of effort for you to look at it. I wish somebody would do a tutorial on these more complex scenarios. I bet a lot of us need to figure this out. A service class is a great idea, and doing at save/create/edit is probably key. Thanks!
It's almost amusing. I wasn't daunted by the number of fields. I figured a few gets, a few sets, throw them in the right places with a
afterStateUpdated
and we're golden! Ha, I wish.Well, in a gist is better, sometimes it’s hard to help, no matter the code size, without a overall view
Tutorials are hard when it comes to specific use cases and app/business logic though.
Sometimes it works out that way. 🙂
You can DM about consulting too. There's always that option LOL
If you have a link to the repo I’ll look at it. You can dm it to me if you don’t want to share it here.
Just have to tell me which files I need to look at specifically. Lol.