When Filter Logic Doesn’t Match Display Logic

๐Ÿ“– 3 minutes read

Ever get a bug report that sounds impossible? “I filtered by SGD, but I’m still seeing USD records in the table.”

You double-check the filter. It works. You check the data. It’s correct. But the user is right โ€” something’s broken.

Here’s what happened: the filter queries one field, but the UI displays a different field.

The Bug Pattern

In our case, we had a pricing dashboard with a currency filter. The filter queried pricing_configs.currency, but the table’s Currency column displayed pricing_batches.default_currency.

Different fields. Different data sources. Completely different results.

A record might have pricing_configs.currency = 'SGD' but pricing_batches.default_currency = 'USD'. When you filter by SGD, the filter includes it (because pricing_configs.currency matches). But the Currency column shows USD (because that’s what pricing_batches.default_currency contains).

From the user’s perspective: “I filtered by SGD. Why am I seeing USD?”

The Fix

Make the filter query the exact same field that’s displayed in the UI.

If your Currency column shows project.budget_currency, your filter must query project.budget_currency. Not client.default_currency. Not invoice.currency. The exact same field.

// โŒ BAD: Filter and display query different sources
$query->where('client.default_currency', $request->currency);
// But the table shows: $record->project->budget_currency

// โœ… GOOD: Filter matches display logic
$query->whereHas('project', function ($q) use ($request) {
    $q->where('budget_currency', $request->currency);
});
// And table shows: $record->project->budget_currency

Why This Happens

Usually because the table evolved over time:

  • Original implementation showed client.default_currency
  • Later, someone changed the display to show project.budget_currency (better UX)
  • But nobody updated the filter logic to match

Or because the data model is complex โ€” multiple currency fields across relationships, and different parts of the code made different assumptions about which one to use.

How to Avoid It

1. Document what each column displays.

Don’t just write “Currency” in the table header. Document it:

// In your table config or component
'columns' => [
    'currency' => [
        'label' => 'Currency',
        'source' => 'project.budget_currency', // โ† This!
    ],
]

2. Test with mismatched data.

Create test records where client.default_currency = 'SGD' but project.budget_currency = 'USD'. Filter by SGD. What shows up in the Currency column? If you see USD, your filter doesn’t match your display.

3. Make an intentional decision.

If you have multiple currency fields, pick one as the canonical “display currency” for this view. Then use that everywhere โ€” filters, sorting, exports, everything.

Bonus: Complex Fields

What if the displayed field comes from a JSON column or computed value?

// Displayed value is extracted from JSON
'currency' => $record->task_config['payment']['currency']

// Filter must extract the same way
$query->whereJsonContains('task_config->payment->currency', $request->currency);

Or better yet, extract it consistently in your model:

// Model accessor
public function getDisplayCurrencyAttribute()
{
    return $this->task_config['payment']['currency'] ?? 'USD';
}

// Now both filter and display use the same accessor
$query->whereRaw("JSON_EXTRACT(task_config, '$.payment.currency') = ?", [$request->currency]);
// Display: $record->display_currency

The Takeaway

When users report “the filter doesn’t work,” don’t just check if the filter query is valid. Check if it matches what’s actually displayed in the table.

Because a working filter that queries the wrong field is worse than a broken one โ€” it silently returns the wrong results.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *