I am new to React, so apologies if this is obvious.
I have several components on a single-page website. This is my App.js:
function App() {
return (
<>
<Header />
<Nav />
<About />
<Experience />
<Portfolio />
<Activities />
<Contact />
<Footer />
</>
);
}
This is the Nav component:
const Nav = () => {
const [activeNav, setActiveNav] = useState('#');
return (
<nav>
<a href='#' onClick={() => setActiveNav('#')} className={activeNav === '#'? 'active': ''}><BiHome/></a>
<a href='#about' onClick={() => setActiveNav('#about')} className={activeNav === '#about'? 'active': ''}><AiOutlineUser/></a>
<a href='#experience' onClick={() => setActiveNav('#experience')} className={activeNav === '#experience'? 'active': ''}><MdWorkOutline/></a>
<a href='#portfolio' onClick={() => setActiveNav('#portfolio')} className={activeNav === '#portfolio'? 'active': ''}><AiOutlineCode/></a>
<a href='#contact' onClick={() => setActiveNav('#contact')} className={activeNav === '#contact'? 'active': ''}><BiMessageSquareDetail/></a>
</nav>
)
}
As you can see, it has links to the other components. I have styled it as a floating navbar. When the user clicks on a link, they're taken to that section of the page. The link's class is changed to "active" - its CSS gets changed to distinguish it from the rest.
Now I want to do it the other way around. When a user scrolls to a component, say Experience, I want the class of the corresponding link in Nav to be changed to active. How can I do that?
Thanks very much for reading until the end :)
Maybe use the useEffect hook to listen for changes in the scroll position of the page and then updating the state of the activeNav based on the position of the different sections of the page
import { useEffect } from 'react';
const Nav = () => {
const [activeNav, setActiveNav] = useState('#');
useEffect(() => {
const handleScroll = () => {
const aboutSection = document.querySelector('#about');
const experienceSection = document.querySelector('#experience');
const portfolioSection = document.querySelector('#portfolio');
const contactSection = document.querySelector('#contact');
if (aboutSection.getBoundingClientRect().top < window.innerHeight * 0.8 && aboutSection.getBoundingClientRect().top > 0) {
setActiveNav('#about');
} else if (experienceSection.getBoundingClientRect().top < window.innerHeight * 0.8 && experienceSection.getBoundingClientRect().top > 0) {
setActiveNav('#experience');
} else if (portfolioSection.getBoundingClientRect().top < window.innerHeight * 0.8 && portfolioSection.getBoundingClientRect().top > 0) {
setActiveNav('#portfolio');
} else if (contactSection.getBoundingClientRect().top < window.innerHeight * 0.8 && contactSection.getBoundingClientRect().top > 0) {
setActiveNav('#contact');
}
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [setActiveNav]);
return (
<nav>
<a href='#' onClick={() => setActiveNav('#')} className={activeNav === '#'? 'active': ''}><BiHome/></a>
<a href='#about' onClick={() => setActiveNav('#about')} className={activeNav === '#about'? 'active': ''}><AiOutlineUser/></a>
<a href='#experience' onClick={() => setActiveNav('#experience')} className={activeNav === '#experience'? 'active': ''}><MdWorkOutline/></a>
<a href='#portfolio' onClick={() => setActiveNav('#portfolio')} className={activeNav === '#portfolio'? 'active': ''}><AiOutlineCode/></a>
<a href='#contact' onClick={() => setActiveNav('#contact')} className={activeNav === '#contact'? 'active': ''}><BiMessageSquareDetail/></a>
</nav>
)
}
maybe you can use onScroll event to calculate the difference between component's height and page's scrollTop in real time
Related
I am creating a navigation bar with React that visually highlights the active component when it is scrolled into view by the user. So, they scroll to the 'About' component and the user icon would highlight and the home icon would return to normal. It looks like this:
I am trying to do this in 2 steps: (1) using the useInView hook in my App.jsx component to set the 'activeElement' with a useState hook when the user scrolls to the useInView {ref}, and (2) passing the 'activeElement' as a useState variable to the child Nav component via props and useEffect to update activeNav in the Nav component when the user is scrolling.
Here is my code for the App component, which I have been testing within the paragraph tags. Currently, activeElement is not being affected by scrolling.
const App = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
const [activeElement, setActiveElement] = useState('#')
return (
<>
<Header ref={ref} setActiveElement={inView ? '#home' : '' }/>
<Nav activeElement={activeElement}/>
<About ref={ref} setActiveElement={inView ? '#about' : '' } />
<p>{activeElement}</p>
<Services ref={ref} setActiveElement={inView ? '#services' : '' } />
<Contact ref={ref} setActiveElement={inView ? '#contact' : '' }/>
<Footer />
<p>{activeElement}</p>
</>
)
}
export default App
And here is the code for my Nav component:
const Nav = ({activeElement}) => {
const [activeNav, setActiveNav] = useState('#');
useEffect(() => {
setActiveNav(activeElement);
})
return (
<nav>
<a href="#" onClick={() => setActiveNav('#')} className={activeNav === '#' ? 'active' : ''}><AiOutlineHome /></a>
<a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><AiOutlineUser /></a>
<a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiOutlineBookOpen /></a>
<a href="#services" onClick={() => setActiveNav('#services')} className={activeNav === '#services' ? 'active' : ''}><FaUncharted /></a>
<a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><RiMessage2Line /></a>
</nav>
)
}
export default Nav
What is wrong with my useInView execution? And am I passing the activeElement variable to the Nav component correctly?
Thanks for taking the time to read this through.
** SOLUTION: **
After some digging, and with thanks to the commenter, Ali Mirzaei, for helping to identify where the issue was occuring, we found 2 problems:
I needed a separate useInView hook for each element being observed.
Using 'ref' on a component call was creating an error: "Warning: Function components cannot be given refs. Attempts to access this ref will fail." So, I used the answer from https://stackoverflow.com/a/65756885/13471663 to pass the ref as a prop named innerRef
Working code is as follows:
const App = () => {
const { ref, inView } = useInView();
const { ref: ref1, inView: inView1 } = useInView();
const { ref: ref2, inView: inView2 } = useInView();
const { ref: ref3, inView: inView3 } = useInView();
const { ref: ref4, inView: inView4 } = useInView();
const [activeElement, setActiveElement] = useState('#')
useEffect(() => {
if (inView) {
setActiveElement('#home');
console.log('home');
};
if (inView1) {
setActiveElement('#about')
console.log('about');
};
if (inView2) {
setActiveElement('#experience')
console.log('experience');
};
if (inView3) {
setActiveElement('#services')
console.log('services');
};
if (inView4) {
setActiveElement('#contact')
console.log('contact');
};
}, [inView, inView1, inView2, inView3, inView4])
return (
<>
<Header innerRef={ref} />
<Nav activeElement={activeElement}/>
<About innerRef={ref1} />
<p>{activeElement} {inView.toString()}</p>
<Experience innerRef={ref2} />
<Services innerRef={ref3} />
<Contact innerRef={ref4} />
<Footer />
<p>{activeElement}</p>
</>
)
}
export default App
And for the Nav component:
const Nav = ({activeElement}) => {
const [activeNav, setActiveNav] = useState('#home');
useEffect(() => {
setActiveNav(activeElement);
})
return (
<nav>
<a href="#" onClick={() => setActiveNav('#home')} className={activeNav === '#home' ? 'active' : ''}><AiOutlineHome /></a>
<a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><AiOutlineUser /></a>
<a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiOutlineBookOpen /></a>
<a href="#services" onClick={() => setActiveNav('#services')} className={activeNav === '#services' ? 'active' : ''}><FaUncharted /></a>
<a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><RiMessage2Line /></a>
</nav>
)
}
export default Nav
And here is an example of the innerRef use from a component:
const About = ({ innerRef }) => {
return (
<section id='about'>
<div ref={innerRef}>
About
</div>
</section>
)
}
Hope that helps anyone out there experiencing the same issue!
fisrt of all what is setActiveElement={inView ? '#home' : '' } in your components?
you have to pass different refs for each component you want to track if it's in the viewport so form react-intersection-observer documents:
import * as React from "react";
// #ts-ignore Wrong type
import { createRoot } from "react-dom/client";
import { useInView } from "react-intersection-observer";
import ScrollWrapper from "./elements/ScrollWrapper";
import "./styles.css";
function App() {
const { ref, inView } = useInView({
threshold: 0
});
const { ref: ref2, inView: inView2 } = useInView({
threshold: 0
});
return (
<ScrollWrapper inView={inView}>
<div ref={ref} className="inview-block">
<h2>
Element is inside the viewport: <strong>{inView.toString()}</strong>
</h2>
</div>
<div ref={ref2} className="inview-block">
<h2>
Element is inside the viewport: <strong>{inView2.toString()}</strong>
</h2>
</div>
</ScrollWrapper>
);
}
const root = createRoot(document.getElementById("root"));
root.render(<App />);
I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.
I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.
readMore is used to hide/show the content and
activeIndex is used to keep track of clicked item index.
This is what I have done so far:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [readMore, setReadMore] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{readMore ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
setActiveIndex(index);
if (activeIndex === id) {
setReadMore(!readMore);
}
}}
>
{readMore ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
The problem is that when I click one button it toggles all the items in the list.
I want to show/hide content only when its own button clicked.
Can somebody tell me what I am doing wrong?
Thanks in advance.
Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndex === index ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
if (activeIndex) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndices, setActiveIndices] = useState(new Set());
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
const newIndices = new Set(activeIndices);
if (activeIndices.has(index)) {
newIndices.delete(index);
} else {
newIndices.add(index);
}
setActiveIndices(newIndices);
}}
>
{activeIndices.has(index) ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Try this
{readMore && (activeIndex === id) ? description : `${description.substring(0, 250)}...`}
function Destination() {
const travels = [
{
title: "Home"
},
{
title: "Traveltype",
subItems: ["Local", "National", "International"]
},
{
title: "Contact",
subItems: ["Phone", "Mail", "Chat"]
}
];
const [activeIndex, setActiveIndex] = useState(null);
return (
<div className="menu-wrapper">
{travels.map((item, index) => {
return (
<div key={`${item.title}`}>
{item.title}
{item.subItems && (
<button
onClick={() => {
if (activeIndex) {
if (activeIndex !== index) {
setActiveIndex(index);
} else {
setActiveIndex(null);
}
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? `Hide` : `Expand`}
</button>
)}
{activeIndex === index && (
<ul>
{item.subItems &&
item.subItems.map((subItem) => {
return (
<li
key={`li-${item.title}-${subItem}`}
>
{subItem}
</li>
);
})}
</ul>
)}
</div>
);
})}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I am developing a web application using MERN stack. I have implemented pagination using React & Bootstrap.
WHAT DO I WANT?
Currently, I have a limited data set. So, the number of pages shown in the pagination are manageable. But with a larger data set, I should be able to limit the number of pages shown in the pagination. The result I am looking for is the pagination implemented at the site www.flanker.net.
The following are my code snippets.
components/Movies.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import MoviesTable from "./MoviesTable";
import { fetchMovies } from "./moviesSlice";
import "./Movies.css";
import Pagination from "./common/Pagination";
import { paginate } from "./../utils/paginate";
const Movies = () => {
const [pageSize, setPageSize] = useState(4);
const [currentPage, setCurrentPage] = useState(1);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchMovies());
}, [dispatch]);
const handlePageChange = (page) => {
setCurrentPage(page);
};
const handlePreviousClick = () => {
setCurrentPage(currentPage - 1);
};
const handleNextClick = () => {
setCurrentPage(currentPage + 1);
};
const { status, movies } = useSelector((state) => state.movies);
const paginatedMovies = paginate(movies, currentPage, pageSize);
let content;
if (status === "loading") {
content = (
<div className="spinner">
<div className="spinner-border text-success">
<span class="sr-only">Loading...</span>
</div>
</div>
);
} else {
content = (
<div className="row">
<div className="col-xs-10 col-md-8 mx-auto mt-3">
{paginatedMovies.length > 0 ? (
<MoviesTable movies={paginatedMovies} />
) : null}
<Pagination
itemsCount={movies.length}
pageSize={pageSize}
currentPage={currentPage}
onPageChange={handlePageChange}
onPreviousClick={handlePreviousClick}
onNextClick={handleNextClick}
/>
</div>
</div>
);
}
return <>{content}</>;
};
export default Movies;
components/pagination.js
import React from "react";
import _ from "lodash";
const Pagination = ({
itemsCount,
pageSize,
onPageChange,
currentPage,
onPreviousClick,
onNextClick,
}) => {
const pageCount = Math.ceil(itemsCount / pageSize);
if (pageCount === 1) return null;
const pages = _.range(1, pageCount + 1);
return (
<nav aria-label="Page navigation example">
<ul className="pagination">
{currentPage !== 1 && (
<li class="page-item">
<a
className="page-link"
style={{ cursor: "pointer" }}
onClick={onPreviousClick}
>
Previous
</a>
</li>
)}
{pages.map((page) => (
<li
key={page}
className={page === currentPage ? "page-item active" : "page-item"}
>
<a
style={{ cursor: "pointer" }}
className="page-link"
onClick={() => onPageChange(page)}
>
{page}
</a>
</li>
))}
{currentPage !== pageCount && (
<li class="page-item">
<a
className="page-link"
style={{ cursor: "pointer" }}
onClick={onNextClick}
>
Next
</a>
</li>
)}
</ul>
</nav>
);
};
export default Pagination;
src/utils/paginate.js
import _ from "lodash";
export function paginate(items, pageNumber, pageSize) {
const startIndex = (pageNumber - 1) * pageSize;
return _(items).slice(startIndex).take(pageSize).value();
}
I did some research in the internet, but could not find a solution. I don't know where to start. I would appreciate any help.
I had the same problems and I resolved them with this algorithm :
handleClick(event) {
this.TotalPage();
this.setState({
currentPage: Number(event.target.id)
});
}
const pageNumbers = 10;
ShowPaginationNumbers(pageNumbers) {
let paginationNumbers = [];
if (pageNumbers) {
let showMax = 3;
let endPage;
let startPage;
if (pageNumbers <= showMax) {
startPage = 1;
endPage = pageNumbers.length;
}
else {
startPage = this.state.currentPage;
if (startPage != pageNumbers.length && (startPage + 1) != pageNumbers.length) {
endPage = this.state.currentPage + showMax - 1;
}
else {
endPage = pageNumbers.length;
}
}
for (let i = startPage; i <= endPage; i++) {
paginationNumbers.push(i);
}
return this.ShowRenderPageNumbers(paginationNumbers);
}
}
ShowRenderPageNumbers(paginationNumbers) {
if (paginationNumbers) {
let result = paginationNumbers.map(number => {
return (
<li className="page-item" >
<a className={(this.state.currentPage === number ? ' active' : '') + ' page-link'} key={number} id={number} onClick={this.handleClick}>{number}</a>
</li>
);
});
return result;
}
}
This is probably really easy, I want to show the info of a list item when a user clicks on the item, however, the code I have will open the info of all list items when clicked.
https://codesandbox.io/s/friendly-lichterman-j1vpd?file=/src/App.js:0-599
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
export default function App() {
const [active, setActive] = useState(false);
return (
<div>
{list.map((item, idx) => {
return (
<>
<li
onClick={() => {
setActive(!active);
}}
>
{item}
<div className={active ? "active" : "info"}>
{" "}
Info {idx + 1}
</div>
</li>
</>
);
})}
</div>
);
}
You are trying to toggle active only which is used by all items. Instead use index position to toggle.
I have explained the rest within the code using comments.
App.js
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
export default function App() {
const [active, setActive] = useState();
return (
<div>
{/* You forgot to add ul here */}
<ul>
{list.map((item, idx) => {
return (
// Added key to each child to avoid error. Use <React.Fragment/> instead of <>
<React.Fragment key={idx}>
<li
onClick={() => {
// Condition for toggling the lists.
// If current list is selected
if (active === idx) {
// change active to blank
setActive();
} else {
// change active to current index
setActive(idx);
}
}}
>
{item}
</li>
<div className={active === idx ? "info active" : "info"}>
{" "}
Info {idx + 1}
</div>
</React.Fragment>
);
})}
</ul>
</div>
);
}
Edited this css to avoid applying to another tag
style.css
.info.active {
display: flex;
}
You can try the above code on sandbox
https://codesandbox.io/s/stackoverflow-qno-65730790-tmeyf
Seems like a good use case for useReducer. It's a really useful tool to keep track of the states of multiple components. In your case you would need to track whether a given LI is active (showing information) or not. Here is how to do that with useReducer along with a Sanbdox
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
const default_states = list.map((item) => Object({ id: item, action: false }));
export default function App() {
const [li_states, dispatch] = React.useReducer((state, id) => {
return state.map((item) => {
if (item.id === id) return { id: item.id, active: !item.active };
else return item;
});
}, default_states);
return (
<div>
{list.map((item) => {
const cur = li_states.find((s) => s.id === item);
return (
<div key={item}>
<li
onClick={() => {
dispatch(item);
}}
>
{item}
</li>
<div className={cur.active ? "action" : "info"}> Info {item}</div>
</div>
);
})}
</div>
);
}
What's happening here is each time you click any of your LIs, the dispatch calls the reducer function inside React.useReducer with the ID of the LI and toggles the state of the clicked LI.
You can follow this method too
const list = ['Start', 'Installation', 'Text Banners', 'Image Banners'];
const links = ['/start', '/installation', '/textbanners', '/imagebanners'];
const [active, setActive] = useState(null)
const toggleActive = (e) => {
console.log(e)
setActive(e.target.innerText)
}
return (
<div className={style.dashboard_container}>
<div className={style.dashboard}>
<ul>
{list.map((item, index) => {
return (
<li className={active == item ? style.active : ''} key={index} onClick={toggleActive}>{item}</li>
)
})}
</ul>
</div>
{children}
</div>
);
I'm making custom tab component and I have issue on closing tab. Switching tab is working ok but when I close last tab I want to set active previouse tab and this is not working.
setTabs (context update) is updating array "data" in object but not "activeTab".
I'm using react Context to store list of tabs and active tab.
Switching tabs is working correctly, opening new tab also works fine.
TabsContext
import React, { useReducer, useEffect } from "react";
const initialState = {
data: [
{
name: "Start",
component: "StartTab",
cantClose: true,
params: null
}
],
activeTab: 0
};
const localState = JSON.parse(localStorage.getItem("tabs"));
const TabsContext = React.createContext();
let reducer = (tabs, newTabs) => {
if (newTabs === null) {
localStorage.removeItem("tabs");
return initialState;
}
return { ...tabs, ...newTabs };
};
function TabsProvider(props) {
const [tabs, setTabs] = useReducer(reducer, localState || initialState);
useEffect(() => {
localStorage.setItem("tabs", JSON.stringify(tabs));
}, [tabs]);
return (
<TabsContext.Provider value={{ tabs, setTabs }}>
{props.children}
</TabsContext.Provider>
);
}
export { TabsContext, TabsProvider };
MainTabs Component:
import React, { Component, useContext } from "react";
import { TabsContext } from "../../providers/TabsProvider";
import StartTab from "./tab.start";
...
class TabComponent extends Component {
components = {
StartTab: StartTab,
...
};
render() {
const TagName = this.components[this.props.tag];
return <TagName />
}
}
const TabsMain = () => {
const { tabs, setTabs } = useContext(TabsContext);
const closeTab = (index) => {
tabs.data.splice(index, 1);
if (tabs.activeTab == tabs.data.length ) {
tabs.activeTab--;
}
setTabs({ data: tabs.data, activeTab: tabs.activeTab });
};
const tabsNavigation = tabs.data.map((tab, index) =>
<li key={index}>
<button
onClick={() => {
setTabs({ data: tabs.data, activeTab: index });
}}
className={`${tabs.activeTab == index ? 'active' : ''}`}
>
{tab.name}
<div onClick={() => {
closeTab(index);
}} className={`close_button ${!tab.cantClose ? 'show' : 'hide'}`}>X</div>
</button>
</li>
);
const tabsPanels = tabs.data.map((tab, index) =>
<div key={index} className={`panel ${tabs.activeTab == index ? 'active' : ''}`}>
<TabComponent tag={tab.component} />
</div>
);
return (
<div className="tabs">
<ul className="tabs__navigation">
{tabsNavigation}
</ul>
<div className="tabs__content">
{tabsPanels}
</div>
</div>
);
};
export default TabsMain;
Navigation Component
import React, { Component, useContext } from "react";
import { TabsContext } from "../../providers/TabsProvider";
const Navigation = () => {
const { tabs, setTabs } = useContext(TabsContext);
const openTab = (newTab) => {
tabs.data.push(newTab);
setTabs(tabs);
};
return (
<ul className="navigation">
<li>
<button onClick={() => { openTab({ name: "Start", component: "StartTab" }); }}>
Start
</button>
</li>
</ul>
);
};
export default Navigation;
I found the solution.
First of all I have double click action:
1) on click on tab - selectTab,
2) on "X" on same button, when i clicked X then selectTab and closeTab are fired (setTabs was fired also two times)
The solution Was to extract "X" from button - from this:
<button
onClick={() => {
setTabs({ data: tabs.data, activeTab: index });
}}
className={`${tabs.activeTab == index ? 'active' : ''}`}
>
{tab.name}
<div onClick={() => {
closeTab(index);
}} className={`close_button ${!tab.cantClose ? 'show' : 'hide'}`}>X</div>
</button>
to this:
<button
onClick={() => {
setTabs({ data: tabs.data, activeTab: index });
}}
className={`${tabs.activeTab == index ? 'active' : ''}`}
>
{tab.name}
</button>
<div onClick={() => {
closeTab(index);
}} className={`close_button ${!tab.cantClose ? 'show' : 'hide'}`}>X</div>
and change closeTab function to this:
const closeTab = (index) => {
tabs.data.splice(index, 1);
setTabs({ data: tabs.data, activeTab: index-1 });
};