onFocus event callback fires with a delay in Kepler card component



:backhand_index_pointing_right: Bug Description


1. Summary

onFocus event callback fires with a delay in Kepler card component

App Name:
Frndly TV App

Bug Severity

  • Impacts operation of app

2. Steps to Reproduce

HomeScreen –> movieGrid(flashList) vertical scrolling of carousels) –> movieCarousel(kepler-ui-carousel ) horizontal scrolling of Kepler cards –> videoTile(card – kepler-ui-component)

We are using multiple array of objects, by default focus is working, we want to control the focus handling as we have custom focus handling scenarios. So we need onFocus to be triggered fastly in card.

Check by clicking the remote button (D-pad) on the card. Add logs inside the card component’s onFocus function—you’ll notice a delay before the logs appear.

3. Observed Behaviour

When we click left or right D-pad button fastly from first card to nth card , focus shifting to nth card is happening with so much delay. Meanwhile if user navigating to any other cards without waiting focus is misbehaving.

4. Expected Behaviour

We need onFocus to trigger faster so the related UI functionalities can be handled without delay

4.a Possible Root Cause & Temporary Workaround

Fill out anything you have tried. If you don’t know, N/A is acceptable

N/A

6. Environment

Please fill out the fields related to your bug below:

  • SDK Version: 0.20.3351

  • OS Information
    NAME=“OS”
    OE_VERSION=“4.0.0”
    OS_MAJOR_VERSION=“1”
    OS_MINOR_VERSION=“1”
    RELEASE_ID=“2”
    OS_VERSION=“1.1”
    BRANCH_CODE=“VegaMainlineTvIntegration”
    BUILD_DESC=“OS 1.1 (VegaMainlineTvIntegration/10153620)”
    BUILD_FINGERPRINT=“1.0.15362.0(c449c30bb1fb0cc8)/10153620N:user/dev-keys”
    BUILD_VARIANT=“user”
    BUILD_TAGS=“dev-keys”
    BUILD_DATE=“Tue Aug 12 00:43:13 UTC 2025”
    BUILD_TIMESTAMP=“1754959393”
    VERSION_NUMBER=“202025362030”

7. Example Code Snippet / Screenshots / Screengrabs

Include any relevant code or component setup in React Native that can help reproduce the bug.

MovieGrid:

    <
    FlashList

ref = {
    flashListRef
}

data = {
    data
}

scrollEnabled = {
    false
}

renderItem = {
    ({
        item,
        index
    }) => renderCarousel(item, index)
}

keyExtractor = {
    (item, index) => `${item.heading}-${index}`
}

estimatedItemSize = {
    calculateEstimatedItemSize(data)
}

estimatedListSize = {
    {

        height: Dimensions.get('screen').height,

        width: Dimensions.get('screen').width,

    }
}

ListEmptyComponent = {
renderEmptyMessage
}

/>

);
const renderCarousel = useCallback(

    (

        item: {

            heading: string;

            data: TitleData[];

            sectioncontrols: TitleData;

        },

        index: number,

    ) => (

        <
        ScrollView

        horizontal = {
            isTV ? false : true
        }

        // scrollEnabled={Platform.isTV ? false : true}

        scrollEnabled = {
            false
        }

        showsHorizontalScrollIndicator = {
            false
        }

        contentContainerStyle = {
            styles.carouselContainer
        }

        nestedScrollEnabled = {
            false
        } >

        <
        MovieCarousel

        from = {
            from
        }

        heading = {
            item.heading
        }

        data = {
            item.data
        }

        sectioncontrols = {
            item.sectioncontrols
        }

        row = {
            index
        }

        cardDimensions = {
            cardDimensionsInner
        }

        onTileFocus = {
            handleCarouselTileFocus
        }

        onTileBlur = {
            onTileBlur
        }

        onTilePress = {
            onTilePress
        }

        isPriorityRow = {
            index < ROWS_SHOWN_ON_SCREEN
        }

        hasTVPreferredFocus = {
            hasTVPreferredFocus
        }

        triggerFetch = {
            triggerFetch
        }

        isFetching = {
            isFetching
        }

        />

        <
        /ScrollView>

    ),

    [cardDimensionsInner, data.length, handleCarouselTileFocus, isTV, onTileBlur, onTilePress],

);
const handleCarouselTileFocus = useCallback(

    (rowIndex ? : number, colIndex ? : number) => {

        if (rowIndex != null) {

            flashListRef.current?.scrollToIndex({

                index: rowIndex,

                animated: true,

                viewPosition: 1, // 0 = top, 0.5 = center, 1 = bottom

            });

        }

        onTileFocus?.(rowIndex, colIndex);

    },

    [onTileFocus],

);



MovieCarousel: 
{
    visibleData.length > 0 && (

        <
        Carousel

        ref = {
            carouselRef
        }

        testID = {
            `carousel-${testID}-${heading}`
        }

        containerStyle = {
            computedStyle.listView
        }

        data = {
            visibleData
        }

        orientation = "horizontal"

        itemDimensions = {
            viewInfos
        }

        itemPadding = {
            30
        }

        renderItem = {
            ItemView
        }

        maxToRenderPerBatch = {
            24
        }

        getItemForIndex = {
            getItemForIndex
        }

        keyProvider = {
            (item, index) => item.searchFeedContentId
        }

        focusIndicatorType = "fixed"

        firstItemOffset = {
            SPACING.firstItemOffset
        }

        itemSelectionExpansion = {
            {

                widthScale: 1.2,

                heightScale: 1.15,

            }
        }

        itemScrollDelay = {
            0.3
        }

        selectionBorder = {
            {

                enabled: true,

                borderColor: '#00AD50',

                borderWidth: 5,

                borderRadius: 5,

                borderStrokeRadius: 0

            }
        }

        />

    )
}
const ItemView = useCallback(

    ({
        item,
        index
    }: CarouselRenderInfo < TitleData > ): JSX.Element => {

        return (

            <
            VideoTile

            from = {
                from
            }

            row = {
                row
            }

            index = {
                index
            }

            key = {
                `${item.id} ${index}`
            }

            data = {
                item
            }

            onFocus = {
                () => onTileFocus && onTileFocus(row, index)
            }

            onBlur = {
                () => onTileBlur && onTileBlur(row, index)
            }

            onPress = {
                () => onTilePress && onTilePress(item, row, index)
            }

            tilePosition = {
                calculateTilePosition(index)
            }

            sectioncontrols = {
                sectioncontrols
            }

            iscarouselCard = {
                true
            }

            hasTVPreferredFocus = {
                hasTVPreferredFocus
            }

            triggerFetch = {
                triggerFetch
            }

            isFetching = {
                isFetching
            }

            />

        );

    },

    [firstElementRef, onCarouselTileInFocus, onTileBlur, row],

);
const onCarouselTileInFocus = useCallback((columnIndex: number, rowIndex: number, title: TitleData | undefined) => {

        if (onTileFocus) {

            setRowFocused(rowIndex);

        }

    },

    [onTileFocus],

);
VideoTile: < Card

orientation = {
    'vertical'
}

focusedStyle = {
    iscarouselCard ? undefined : styles.focusedCard
}

style = {
    [styles.card]
}

onFocus = {
    onFocusHandler
}

onBlur = {
    onBlurHandler
}

hasTVPreferredFocus = {
    (from === 'viewAll' || from === 'search') ? hasTVPreferredFocus : index === 0 && row === 0 ? hasTVPreferredFocus : false
}

onPress = {
        () => {

            if (data.template != null && data.template != undefined) {

                onPress?.(data);

            } else {

                navigatetoTargetPageTypeScreen(index);

            }

        }
    } >

    <
    Image

style = {
    styles.albumArt
}

source = {
    getImageSource(imageUrl)
}

resizeMode = {
    from === "viewAll" ? "stretch" : (data?.cardType === 'network_poster' || data?.cardType === 'network_poster_package')

        ||

        data?.cardType === 'roller_poster'

        ?
        'contain' : 'cover'
}

defaultSource = {

    data.cardType === 'roller_poster'

    ?
    require('../assets/placeholder_roller.jpg')

    :
        data.cardType === 'sheet_poster'

        ?
        require('../assets/placeholder_sheet.jpg')

        :
        require('../assets/placeholder_sheet.jpg')

}

/>

<
/Card>  const onFocusHandler = useCallback(() => {

setIsFocused(true);

if (onFocus) {

    onFocus(data, row, index);

}

if (from === 'viewAll') {

    requestAnimationFrame(() => {

        animateScale(1.02, true);

    });

}

}, [onFocus, data, row, index]);



const onBlurHandler = useCallback(() => {

    setIsFocused(false)

    onBlur?.(data, row, index);

    if (from === 'viewAll') {

        requestAnimationFrame(() => {

            animateScale(1, true);

        });

    }

}, [onBlur, data, row, index]);

Hi @vittalmaradi

Let me try and reproduce this issue and discuss with internal teams for a solution.
I’ll come back to this post if I have more questions while reproducing this.

Warm regards,
Ivy

Hey @vittalmaradi

I tried to build a sample app with your shared code but the app keeps crashing. Could you please share a working code?
Thanks.

Warm regards,
Ivy

@vittalmaradi requesting you to share a working code. I tried building a sample app with your shared code but the app keeps crashing.

Warm regards,
Ivy