Fix null reference errors in feature flags view

- Added null checks throughout filteredAndSortedFlags computation
- Filter out null/undefined entries before processing
- Added null checks in summary statistics calculations
- Enhanced API response filtering to remove invalid entries
- Added optional chaining for safer property access
- Improved error handling for malformed API responses

This resolves the "can't access property 'enabled', f is null" runtime error.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 15:28:02 +02:00
parent 6d151d9e82
commit b364fb9e8f

View File

@@ -61,8 +61,8 @@ const FeatureFlagsViewComponent = () => {
const result = await response.json() const result = await response.json()
// Extract docs array from Payload API response // Extract docs array from Payload API response and filter out null/invalid entries
const flagsArray = result.docs || [] const flagsArray = (result.docs || []).filter((flag: any) => flag && flag.id && flag.name)
// Only update state if the component is still mounted (signal not aborted) // Only update state if the component is still mounted (signal not aborted)
if (!signal?.aborted) { if (!signal?.aborted) {
@@ -131,14 +131,15 @@ const FeatureFlagsViewComponent = () => {
}, [sortField]) }, [sortField])
const filteredAndSortedFlags = useMemo(() => { const filteredAndSortedFlags = useMemo(() => {
let filtered = flags // Filter out null/undefined entries first
let filtered = flags.filter(flag => flag && flag.name)
// Filter by search // Filter by search
if (search) { if (search) {
filtered = flags.filter(flag => filtered = filtered.filter(flag =>
flag.name.toLowerCase().includes(search.toLowerCase()) || flag.name?.toLowerCase().includes(search.toLowerCase()) ||
flag.description?.toLowerCase().includes(search.toLowerCase()) || flag.description?.toLowerCase().includes(search.toLowerCase()) ||
flag.tags?.some(t => t.tag.toLowerCase().includes(search.toLowerCase())) flag.tags?.some(t => t.tag?.toLowerCase().includes(search.toLowerCase()))
) )
} }
@@ -148,8 +149,8 @@ const FeatureFlagsViewComponent = () => {
let bVal: any = b[sortField] let bVal: any = b[sortField]
if (sortField === 'updatedAt') { if (sortField === 'updatedAt') {
aVal = new Date(aVal).getTime() aVal = new Date(aVal || 0).getTime()
bVal = new Date(bVal).getTime() bVal = new Date(bVal || 0).getTime()
} }
if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1 if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1
@@ -246,7 +247,7 @@ const FeatureFlagsViewComponent = () => {
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}> <div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<div style={{ fontSize: '0.875rem', color: '#6b7280' }}> <div style={{ fontSize: '0.875rem', color: '#6b7280' }}>
{filteredAndSortedFlags.length} of {flags.length} flags {filteredAndSortedFlags.length} of {flags.filter(f => f && f.name).length} flags
</div> </div>
<button <button
onClick={() => fetchFlags()} onClick={() => fetchFlags()}
@@ -538,19 +539,19 @@ const FeatureFlagsViewComponent = () => {
color: '#6b7280' color: '#6b7280'
}}> }}>
<div> <div>
<span style={{ fontWeight: '600' }}>Total:</span> {flags.length} flags <span style={{ fontWeight: '600' }}>Total:</span> {flags.filter(f => f && f.name).length} flags
</div> </div>
<div> <div>
<span style={{ fontWeight: '600' }}>Enabled:</span> {flags.filter(f => f.enabled).length} <span style={{ fontWeight: '600' }}>Enabled:</span> {flags.filter(f => f && f.enabled).length}
</div> </div>
<div> <div>
<span style={{ fontWeight: '600' }}>Disabled:</span> {flags.filter(f => !f.enabled).length} <span style={{ fontWeight: '600' }}>Disabled:</span> {flags.filter(f => f && !f.enabled).length}
</div> </div>
<div> <div>
<span style={{ fontWeight: '600' }}>Rolling out:</span> {flags.filter(f => f.enabled && f.rolloutPercentage && f.rolloutPercentage < 100).length} <span style={{ fontWeight: '600' }}>Rolling out:</span> {flags.filter(f => f && f.enabled && f.rolloutPercentage && f.rolloutPercentage < 100).length}
</div> </div>
<div> <div>
<span style={{ fontWeight: '600' }}>A/B Tests:</span> {flags.filter(f => f.variants && f.variants.length > 0).length} <span style={{ fontWeight: '600' }}>A/B Tests:</span> {flags.filter(f => f && f.variants && f.variants.length > 0).length}
</div> </div>
</div> </div>
</div> </div>