Reputation: 9570
I have been through many similar QnAs on StackOverflow but I am still confused about why it does not work for me. I understand that state update is not synchronous. Also I am not performing any DOM manipulation.
The full working demo here which demonstrates the issue - https://codesandbox.io/s/falling-pine-kljujf?file=/src/Tabs.js
In this example I am rendering few tabs. A new tab initially presents a list of DB "table" names (which is fixed for this demo). On selecting the table name, the list is replaced by table's content.
The issue is when you close an open tab the currentTab
state does not update to the (open) tab I am setting to. Because of which the tab's detail area remains blank until I manually click on an open tab's name.
In the above picture I closed the third tab. The expectation was that the tab selection should have auto changed to second tab, but it did not. The code for that same is as below.
function removeTab(id) {
const ntl = tabsList;
const idx = ntl.findIndex((v) => v.id === id);
if (idx !== -1) {
ntl.splice(idx, 1);
if (ntl.length) {
let t = ntl[idx];
console.log("------", t, idx);
if (!t) {
t = ntl[0];
}
console.log("++++++1", t, t.id);
setCurrentTab(t.id);
setTabsList([...ntl]);
} else {
const t = newTab();
console.log("++++++2", t, t.id);
setCurrentTab(t.id);
setTabsList([t]);
}
}
}
Above the passed id
was that of the third tab. The tabsList
state contains an array with data of each tab. currentTab
contains only the id
of the current tab. As per the console.log
statements above the correct tab's id
is passed, but the currentTab never updates. Even if I put a code like below.
useEffect(() => { console.log('------------>', currentTab) }, [currentTab]);
It never fires in this case.
The removeTab
method is invoked from JSX like below.
{tabsList.map((t) => (
<a
key={t.id + ""}
className={
"tab tab-bordered " + (currentTab === t.id ? "tab-active" : "")
}
onClick={() => {
setCurrentTab(t.id);
}}
>
{t.name}
<button
onClick={() => {
removeTab(t.id); // On clicking X button we remove tab
}}
>
X
</button>
</a>
))}
Upvotes: 0
Views: 63
Reputation: 1731
I made a few changes to the code they may help you finding a better solution
Setting the initial values tab in state instead of useEffect, to reduce a render cycles
Creating a new array on delete and add
you can find the modified codesandbox example
Using a single selected item to render the content to prevent duplication
the only issue was react was not flushing the changes on delete, so I had to use a hack, which you may find helpful in such cases
let tabId = 1;
const DisplayTable = ({ isNewMode, name, ...rest }) => {
return (
<div>
{isNewMode ? (
<TableSelection {...rest} />
) : (
<div>Contents of table: {name}</div>
)}
</div>
);
};
export default function Tabs() {
const [tabsList, setTabsList] = useState([
{
id: tabId,
name: "Select table",
isNewMode: true
}
]);
const [currentTab, setCurrentTab] = useState(tabId);
function newTab() {
tabId = tabId + 1; // unique id check
return {
id: tabId,
name: "Select table",
isNewMode: true
};
}
function addTab() {
const t = newTab();
setTabsList((tabs) => [...tabs, t]);
console.log("++++++3", t, t.id);
setCurrentTab(t.id);
}
async function removeTab(id) {
// functional updaters are simpler
// setTabsList(tabs => [...tabs.filter((a) => a.id !== id)])
// need better logic to select the right tab, this seems simeple enough to understand
const deleteIndex = tabsList.findIndex((v) => v.id === id);
//
if (deleteIndex !== -1) {
const filteredItems = tabsList.filter((a) => a.id !== id);
if (deleteIndex > 0) {
const prevId = filteredItems[deleteIndex - 1];
console.log("prevId", prevId);
setTabsList([...filteredItems]);
await Promise.resolve();
setCurrentTab(prevId.id);
}
}
}
const selectedItem = tabsList?.find((item) => item.id === currentTab);
// console.log("Rendering......", currentTab, tabsList);
return (
<div className="p-8 h-full flex flex-col">
<div className="tabs">
{tabsList.map((t) => (
<a
href="#"
key={t.id}
className={
"tab tab-bordered " + (currentTab === t.id ? "tab-active" : "")
}
onClick={(e) => {
e.preventDefault();
setCurrentTab(t.id);
}}
>
{t.name}
<button
className="ml-2 btn btn-ghost btn-square btn-xs"
onClick={(e) => {
e.preventDefault();
removeTab(t.id);
}}
>
X
</button>
</a>
))}
<a href="#" className="tab">
<button
className="btn btn-ghost btn-square btn-xs"
onClick={(e) => {
e.preventDefault();
addTab();
}}
>
+
</button>
</a>
</div>
{selectedItem ? (
<DisplayTable
onTableSelection={(item) => {
tabId = tabId + 1;
const d = { ...item, id: tabId, isNewMode: false };
setTabsList((t) => [...t, d]);
console.log("table select", d);
setCurrentTab(d.id);
}}
{...selectedItem}
/>
) : null}
{/* {tabsList.map((t) => (
<div key={t.id}>
<TableSelection />
</div>
))} */}
</div>
);
}
Hope is helps in some way,
Cheers
Upvotes: 0
Reputation: 111
There are two problems
Here is the edits i made to the codesandbox https://codesandbox.io/s/practical-platform-dvkxrq?file=/src/Tabs.js:3723-3738
Upvotes: 1