Skip to content
48 changes: 48 additions & 0 deletions app/components/Views/TrendingView/ExploreActiveTabContext.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { render, screen } from '@testing-library/react-native';
import { Text } from 'react-native';
import {
ExploreActiveTabProvider,
useExploreActiveTab,
} from './ExploreActiveTabContext';

const ActiveTabProbe: React.FC = () => {
const activeTab = useExploreActiveTab();
return <Text testID="active-tab">{activeTab}</Text>;
};

describe('ExploreActiveTabContext', () => {
it('defaults to "Now" when rendered outside a provider', () => {
render(<ActiveTabProbe />);

expect(screen.getByTestId('active-tab')).toHaveTextContent('Now');
});

it('exposes the provided active tab to descendants', () => {
render(
<ExploreActiveTabProvider activeTab="Macro">
<ActiveTabProbe />
</ExploreActiveTabProvider>,
);

expect(screen.getByTestId('active-tab')).toHaveTextContent('Macro');
});

it('updates descendants when the active tab changes', () => {
const { rerender } = render(
<ExploreActiveTabProvider activeTab="Now">
<ActiveTabProbe />
</ExploreActiveTabProvider>,
);

expect(screen.getByTestId('active-tab')).toHaveTextContent('Now');

rerender(
<ExploreActiveTabProvider activeTab="Sports">
<ActiveTabProbe />
</ExploreActiveTabProvider>,
);

expect(screen.getByTestId('active-tab')).toHaveTextContent('Sports');
});
});
39 changes: 39 additions & 0 deletions app/components/Views/TrendingView/ExploreActiveTabContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, {
createContext,
useContext,
type PropsWithChildren,
} from 'react';
import type { ExploreTabName } from './search/analytics';

const DEFAULT_ACTIVE_TAB: ExploreTabName = 'Now';

const ExploreActiveTabContext =
createContext<ExploreTabName>(DEFAULT_ACTIVE_TAB);

export interface ExploreActiveTabProviderProps extends PropsWithChildren {
activeTab: ExploreTabName;
}

/**
* Exposes the currently active Explore tab to descendants without threading
* it through `TabProps`. `TabsList` keeps every tab mounted simultaneously,
* so passing the active tab as a prop would give all six tabs a changing
* prop value on every switch, busting memoization for feeds that don't care
* which tab is active. Consumers that do care (e.g. `PerpsBlock` pausing its
* live subscription while hidden) read it via `useExploreActiveTab` instead.
*/
export const ExploreActiveTabProvider: React.FC<
ExploreActiveTabProviderProps
> = ({ activeTab, children }) => (
<ExploreActiveTabContext.Provider value={activeTab}>
{children}
</ExploreActiveTabContext.Provider>
);

/**
* The currently active Explore tab name (e.g. `'Now'`, `'Macro'`). Falls
* back to `'Now'` when rendered outside a provider, matching the tabs list's
* default first tab.
*/
export const useExploreActiveTab = (): ExploreTabName =>
useContext(ExploreActiveTabContext);
123 changes: 66 additions & 57 deletions app/components/Views/TrendingView/TrendingView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { TouchableOpacity } from 'react-native';
import {
RouteProp,
Expand Down Expand Up @@ -30,6 +30,7 @@ import { selectBasicFunctionalityEnabled } from '../../../selectors/settings';
import BasicFunctionalityEmptyState from '../../UI/BasicFunctionality/BasicFunctionalityEmptyState/BasicFunctionalityEmptyState';
import TrendingFeedSessionManager from '../../UI/Trending/services/TrendingFeedSessionManager';
import ExploreSearchBar from './components/ExploreSearchBar/ExploreSearchBar';
import { ExploreActiveTabProvider } from './ExploreActiveTabContext';
import { useExploreRefresh } from './hooks/useExploreRefresh';
import NowTab from './tabs/NowTab';
import MacroTab from './tabs/MacroTab';
Expand Down Expand Up @@ -134,6 +135,7 @@ export const ExploreFeed: React.FC = () => {
const sessionManager = TrendingFeedSessionManager.getInstance();
const previousTabRef = useRef<ExploreTabName>('Now');
const pendingExploreEntrySourceRef = useRef<string | undefined>(undefined);
const [activeTab, setActiveTab] = useState<ExploreTabName>('Now');

const handleTabChange = useCallback(({ i }: { i: number }) => {
const destinationTab = TAB_NAMES[i];
Expand All @@ -147,6 +149,7 @@ export const ExploreFeed: React.FC = () => {
...(source ? { source } : {}),
});
previousTabRef.current = destinationTab;
setActiveTab(destinationTab);
}, []);

// Handle tab navigation from route params
Expand Down Expand Up @@ -237,63 +240,69 @@ export const ExploreFeed: React.FC = () => {
{!isBasicFunctionalityEnabled ? (
<BasicFunctionalityEmptyState />
) : (
<TabsList
ref={tabsListRef}
testID="explore-tabs"
tabsListContentTwClassName="px-0 mt-0"
onChangeTab={handleTabChange}
>
<Box
key="now"
twClassName="flex-1"
{...({ tabLabel: strings('trending.tabs.now') } as TabViewProps)}
>
<NowTab {...tabProps} />
</Box>
<Box
key="macro"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.macro'),
} as TabViewProps)}
>
<MacroTab {...tabProps} />
</Box>
<Box
key="rwas"
twClassName="flex-1"
{...({ tabLabel: strings('trending.tabs.rwas') } as TabViewProps)}
>
<RwasTab {...tabProps} />
</Box>
<Box
key="crypto"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.crypto'),
} as TabViewProps)}
>
<CryptoTab {...tabProps} />
</Box>
<Box
key="sports"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.sports'),
} as TabViewProps)}
>
<SportsTab {...tabProps} />
</Box>
<Box
key="dapps"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.dapps'),
} as TabViewProps)}
<ExploreActiveTabProvider activeTab={activeTab}>
<TabsList
ref={tabsListRef}
testID="explore-tabs"
tabsListContentTwClassName="px-0 mt-0"
onChangeTab={handleTabChange}
>
<DappsTab {...tabProps} />
</Box>
</TabsList>
<Box
key="now"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.now'),
} as TabViewProps)}
>
<NowTab {...tabProps} />
</Box>
<Box
key="macro"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.macro'),
} as TabViewProps)}
>
<MacroTab {...tabProps} />
</Box>
<Box
key="rwas"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.rwas'),
} as TabViewProps)}
>
<RwasTab {...tabProps} />
</Box>
<Box
key="crypto"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.crypto'),
} as TabViewProps)}
>
<CryptoTab {...tabProps} />
</Box>
<Box
key="sports"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.sports'),
} as TabViewProps)}
>
<SportsTab {...tabProps} />
</Box>
<Box
key="dapps"
twClassName="flex-1"
{...({
tabLabel: strings('trending.tabs.dapps'),
} as TabViewProps)}
>
<DappsTab {...tabProps} />
</Box>
</TabsList>
</ExploreActiveTabProvider>
)}
</Box>
</Box>
Expand Down
Loading
Loading