mirror of
https://github.com/xtr-dev/payload-feature-flags.git
synced 2025-12-10 02:43:25 +00:00
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:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user