Skip to content

Commit 2db0627

Browse files
authored
fixes: Blank channel page edge case, prevents duplicate history entries on navigation (#3410)
Ensures navigation actions triggered by buttons and tab changes do not result in duplicate entries in browser history. This prevents unnecessary back/forward navigation clutter. Loading indicators are adjusted to prevent flickering when switching between tabs or when sections are empty. Fixes an issue where empty playlist sections would not display the "No Content Found" message.
1 parent 115b47e commit 2db0627

File tree

4 files changed

+68
-19
lines changed

4 files changed

+68
-19
lines changed

ui/component/button/view.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
247247
disabled={disable}
248248
onClick={(e) => {
249249
e.stopPropagation();
250+
// Prevent duplicate history entries when navigating to the current page
251+
const currentPath = window.location.pathname + window.location.search;
252+
if (path === currentPath || path === window.location.pathname) {
253+
e.preventDefault();
254+
}
250255
if (onClick) {
251256
onClick();
252257
}

ui/page/claim/internal/claimPageComponent/internal/channelPage/tabs/homeTab/internal/homeTabSection/view.jsx

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,31 @@ function HomeTabSection(props: Props) {
229229
}
230230
}
231231

232-
const isLoading =
233-
(fetchingClaimSearch || !requiresSearch) &&
234-
section.type &&
235-
section.type !== 'featured' &&
236-
!claimSearchResults &&
237-
!collectionClaimIds &&
238-
!featuredChannels;
232+
// Determine if we should show loading state based on section type:
233+
// - Sections requiring search: only show loading if actively fetching
234+
// - 'playlist' type: only show loading if claim_id exists and data is pending
235+
// - 'channels' type: don't show loading (data either exists or doesn't)
236+
// - If no claim_id for playlist, or no data source configured, don't show loading
237+
const isLoading = (() => {
238+
if (!section.type || section.type === 'featured') return false;
239+
240+
if (requiresSearch) {
241+
// Only show loading during active fetch, not when results are empty/missing
242+
return fetchingClaimSearch === true;
243+
}
244+
245+
// For 'playlist' type: need claim_id to load anything
246+
if (section.type === 'playlist') {
247+
return section.claim_id && !collectionClaimIds && !collectionUrls;
248+
}
249+
250+
// For 'channels' type: don't show loading skeleton
251+
if (section.type === 'channels') {
252+
return false;
253+
}
254+
255+
return false;
256+
})();
239257

240258
return (
241259
<div className="home-section-content">
@@ -393,6 +411,14 @@ function HomeTabSection(props: Props) {
393411
</div>
394412
</div>
395413
)}
414+
{!isLoading &&
415+
!editMode &&
416+
((requiresSearch && claimSearchResults && claimSearchResults.length === 0) ||
417+
(section.type === 'playlist' && !section.claim_id)) && (
418+
<div className="section">
419+
<div className="empty empty--centered">{__('No Content Found')}</div>
420+
</div>
421+
)}
396422
{section.type &&
397423
(section.claim_id ||
398424
collectionUrls ||

ui/page/claim/internal/claimPageComponent/internal/channelPage/view.jsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,44 +259,50 @@ function ChannelPage(props: Props) {
259259
function onTabChange(newTabIndex, keepFilters) {
260260
const baseUrl = formatLbryUrlForWeb(uri);
261261
const url = isEmbedPath ? `/$/embed${baseUrl}` : baseUrl;
262-
let search = '';
262+
let newSearch = '';
263263

264264
if (!keepFilters) setFilters(undefined);
265265

266266
switch (newTabIndex) {
267267
case 0:
268-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.HOME}`;
268+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.HOME}`;
269269
break;
270270
case 1:
271-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.CONTENT}`;
271+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.CONTENT}`;
272272
break;
273273
case 2:
274-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.SHORTS}`;
274+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.SHORTS}`;
275275
break;
276276
case 3:
277-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.PLAYLISTS}`;
277+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.PLAYLISTS}`;
278278
break;
279279
case 4:
280-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.CHANNELS}`;
280+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.CHANNELS}`;
281281
break;
282282
case 5:
283283
if (!isOdyseeChannel) {
284-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.MEMBERSHIP}`;
284+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.MEMBERSHIP}`;
285285
}
286286
break;
287287
case 6:
288-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.DISCUSSION}`;
288+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.DISCUSSION}`;
289289
break;
290290
case 7:
291291
if (!hideAboutTab) {
292-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.ABOUT}`;
292+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.ABOUT}`;
293293
}
294294
break;
295295
case 8:
296-
search += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.SETTINGS}`;
296+
newSearch += `?${CHANNEL_PAGE.QUERIES.VIEW}=${CHANNEL_PAGE.VIEWS.SETTINGS}`;
297297
break;
298298
}
299-
push(`${url}${search}`);
299+
300+
// Only push if the URL is actually changing to avoid duplicate history entries
301+
const newFullUrl = `${url}${newSearch}`;
302+
const currentFullUrl = `${pathname}${search}`;
303+
if (newFullUrl !== currentFullUrl) {
304+
push(newFullUrl);
305+
}
300306
}
301307

302308
React.useEffect(() => {

ui/store.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@ import { persistOptions } from 'redux/setup/persistedState';
1414
import { sharedStateMiddleware } from 'redux/setup/sharedState';
1515
import { tabStateSyncMiddleware } from 'redux/setup/tabState';
1616

17-
const history = createBrowserHistory();
17+
const browserHistory = createBrowserHistory();
18+
19+
// Wrap history.push to prevent duplicate entries
20+
const originalPush = browserHistory.push.bind(browserHistory);
21+
browserHistory.push = (path, state) => {
22+
const currentPath = browserHistory.location.pathname + browserHistory.location.search;
23+
const newPath = typeof path === 'string' ? path : path.pathname + (path.search || '');
24+
if (newPath !== currentPath) {
25+
originalPush(path, state);
26+
}
27+
};
28+
29+
const history = browserHistory;
1830
const rootReducer = createRootReducer(history);
1931
const persistedReducer = persistReducer(persistOptions, rootReducer);
2032
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

0 commit comments

Comments
 (0)