Problem
When TabPanel's title prop changes (new reference), the useEffect with [props.id, props.title] dependencies fires:
- Cleanup runs
unregisterTab(id) — removes the tab from state
- Effect runs
registerTab({id, title}) — appends to end via concat
The reducer's registerTab checks tabWasAlreadyRegistered by ID, but since cleanup already removed it, the tab gets appended to the end — causing visible tab reordering.
This means passing JSX as title (which creates a new reference on every render) causes the tab to jump to the end on every render.
Expected Behavior
When a tab with the same ID re-registers (e.g. due to title change), it should retain its position in the tab list.
Suggested Fix
In the reducer's registerTab case, if a tab with the same ID already exists, update its title in-place instead of skipping:
case 'registerTab': {
const existingIndex = state.tabs.findIndex((tab) => tab.id === action.newTab.id);
if (existingIndex !== -1) {
const updatedTabs = [...state.tabs];
updatedTabs[existingIndex] = action.newTab;
return { ...state, tabs: updatedTabs };
}
return {
tabs: state.tabs.concat(action.newTab),
activeTabID: state.tabs.length === 0 ? action.newTab.id : state.activeTabID,
};
}
Reproduction
Pass a JSX element as title that depends on changing state:
<TabPanel title={<MyTitle hasIcon={someChangingState} />} id="my-tab">
Every time someChangingState changes, the tab moves to the end of the tab list.
Workaround
Use a stable string as title instead of JSX.
Problem
When
TabPanel'stitleprop changes (new reference), theuseEffectwith[props.id, props.title]dependencies fires:unregisterTab(id)— removes the tab from stateregisterTab({id, title})— appends to end viaconcatThe reducer's
registerTabcheckstabWasAlreadyRegisteredby ID, but since cleanup already removed it, the tab gets appended to the end — causing visible tab reordering.This means passing JSX as
title(which creates a new reference on every render) causes the tab to jump to the end on every render.Expected Behavior
When a tab with the same ID re-registers (e.g. due to title change), it should retain its position in the tab list.
Suggested Fix
In the reducer's
registerTabcase, if a tab with the same ID already exists, update its title in-place instead of skipping:Reproduction
Pass a JSX element as
titlethat depends on changing state:Every time
someChangingStatechanges, the tab moves to the end of the tab list.Workaround
Use a stable string as
titleinstead of JSX.