Just starting out with react/typescript (my third project).
I'm currently working on a custom dynamic tab component. In this component, I dynamically create a class name to determine which tab is active and which is not. It seems to be functioning properly, but upon closer inspection, there are some errors.
The goal is to add a background color when the active tab is clicked to indicate to the user that it's the tab they're currently viewing. The full code is provided below, but I've highlighted the issues in these sections:
- I've set up a state that changes based on the clicked tab. When a tab is clicked, the active tab state is updated to match the current tab's index. However, although the logic seems sound, the console shows that the active tab id doesn't always match the current tab's index.
const [btnState, setBtnState] = useState({activeTabId: 0});
const onClick = (id:number) => {
setBtnState({activeTabId:id})
console.log(btnState.activeTabId, id);
};
- After researching online resources, it became clear that the state wasn't updating instantaneously. Once I made adjustments, the console log showed that the active id was now being updated correctly with the current tab. However, the CSS behavior wasn't as expected - instead of changing the class name to "active tab" when clicking on the current tab, it would only display on the previous tab.
const [btnState, setBtnState] = useState({activeTabId: 0});
const onClick = (id:number) => {
btnState.activeTabId = id
setBtnState(btnState)
console.log(btnState.activeTabId, id);
};
Can anyone shed light on what might be going wrong? Why does the first approach work despite the inaccurate active id, while the second one somewhat works with the correct active id? The problematic tag here is <li>
import React, { ReactNode } from "react"
import { useState } from "react"
function MyComponent (props: ComponentProps) {
const [tab, setTab] = useState([{ title: ""}])
const [btnState, setBtnState] = useState({activeTabId: 0});
const newTab = () => {
setTab([...tab, {title: ""}])
}
const deleteTab = (index:number) => {
const list = [...tab]
list.splice(index, 1)
setTab(list)
}
const handleTabChange = (e:React.ChangeEvent<HTMLInputElement>, index:number) => {
const {name, value} = e.target
const list = [...tab]
list[index][name] = value
setTab(list)
}
const updateActiveId= (id:number) => {
setBtnState({activeTabId:id})
console.log(btnState.activeTabId, id);
};
const activeNav = (id:number) => {
if (id === btnState.activeTabId) {
return "individual-tab-container tab-selected";
} else {
return "individual-tab-container";
}
};
return (
<div className="dynamic-tabs">
<ul className="all-tabs">
{tab.map((tabTitle, index) => (
<span className="dynamic-tab-container">
<li key={index} onClick={() => updateActiveId(index)} className={activeNav(index)}>
<span className="title-close-save-button-container">
<input className="title-of-tab"
name="title"
type="text"
required
placeholder="Click to rename tab here"
size={20}
maxLength={20}
value = {tabTitle.title}
onChange={(e) => handleTabChange(e, index)}/>
<div className="save-close-btn-container">
<div className="save-button-container">
<button id="save">save</button></div>
<div className="close-btn-container">
<button type="button" className="close-btn" onClick={() => deleteTab(index)}>
<div className="close-btn-txt">×</div>
</button>
</div>
</div>
</span>
</li>
</span>
))}
</ul>
<span className="new-tab-btn-container">
<button type='button' className="new-tab-btn" onClick={newTab}>
<i className="material-icons" id="add-btn">add</i></button>
</span>
</div>
)
}
CSS - used for testing the functionality of the active tab
.dynamic-tabs{
position: relative;
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
height: 120px;
border: 2px solid green;
width:94%;
}
.dynamic-tab-container{
display: flex;
}
li {
list-style-type: none;
}
.all-tabs{
display: flex;
height: 80px;
}
.dynamic-tab-container{
border-bottom: 2px solid #d3d2d2;
border-right: 2px solid black;
border-left: 2px solid black;
border-top: 2px solid black;
margin-top: 7px;
height: 110%;
width: 360px;
}
.title-close-save-button-container{
display: flex;
margin-top:6.5%;
flex-flow: row; */
}
input{
margin-left:2%;
margin-top:4%;
border:none;
font-family: 'Hi Melody', cursive;
font-size: 14px;
font-weight:500;
}
input:focus{
outline:none;
}
button{
background: none;
border: none;
}
button:active{
outline: none;
}
button:focus{
outline: none;
}
.save-close-btn-container{
display: flex;
margin-left: 16%;
margin-top: 4%;
transition: margin-left 200ms;
font-family: 'Hi Melody', cursive;
font-weight:300;
}
.new-tab-btn-container{
display: flex;
position: fixed;
margin-left: 95%;
top:7%;
height:60px;
border-right: 2px solid black;
border-left: 2px solid black;
border-top: 2px solid black;
border-bottom: 2px solid black;
}
.tab-selected{
background-color: red;
}