Set a class name in React based on a button value
In this example, tabs, comprised of an unordered list of button elements, control what content is shown (defined elsewhere). This uses the value
of the <button>
element within each tab to determine which tab was clicked by tying an event handler to the click. It then calls the function to change the content the tab is controlling.
For the sake of brevity it omits aria-controls
attributes and any other attributes that don't help directly illustrate the point. See w3.org for a table of relevant attributes that would likely be applied to this tabbed navigation.
import { useState } from 'react';
import PropTypes from 'prop-types';
function ListItem({ item, value, clickEventHandler, activeButton }) {
const activeClassName = 'active';
return(
<li className={`nav-tab ${activeButton === item ? activeClassName : ''}`}>
<button className={`${activeButton === item ? activeClassName : ''}`} name="nav-tabs" value={item} onClick={clickEventHandler}>{value}</button>
</li>
)
}
function NavTabs({ changeEventHandler }) {
// useState() to store which button is active
const [activeButton, setActiveButton] = useState(null);
// modify state for the active button before passing on to the onChange event handler
const handleButtonClick = (event) => {
// store the click event's target's value
setActiveButton(event.target.value);
// modify the content shown based on the tab clicked (calls a function that has been passed in from elsewhere)
changeEventHandler(event);
};
// mock up options that should really be queried from somewhere
// the key, e.g. 'item-1', is what is set as the `value` on the button
// it is also what is checked against when setting the active state for the button and the button's containing list element.
const items = [
{ 'item-1': 'Item 1' },
{ 'item-2': 'Item 2' }
];
return(
<ul>
{items.map((itemObj, index) => {
const item = Object.keys(itemObj)[0];
const value = itemObj[item];
return (
<ListItem
key={index}
item={item}
value={value}
clickEventHandler={handleButtonClick}
activeButton={activeButton}
/>
);
})}
</ul>
);
}
// Define prop types
ListItem.propTypes = {
item: PropTypes.string,
value: PropTypes.string,
clickEventHandler: PropTypes.func,
activeButton: PropTypes.object,
}
NavTabs.propTypes = {
onChange: PropTypes.func,
}
Clearing the active state by clicking off or pressing escape
Since neither tab is selected by default, if you click off, or press escape, you might expect the tabs to return to their default state. To do this, import useEffect
from React along with useState
, then in the NavTabs
component, add below the definition of activeButton
and setActiveButton
:
const handleWindowClick = (event) => {
// Check if the click is outside the React component
if (
!event.target.closest('li')
&& !event.target.closest('button')
) {
// set neither button to active
setActiveButton(null);
// modify the content shown based on the tab clicked (calls a function that has been passed in from elsewhere)
changeEventHandler(event);
}
};
// Event handler for window keydown
const handleWindowKeydown = (event) => {
// Check if the key pressed was the escape key
if (event.key === 'Escape') {
// set neither button to active
setActiveButton(null);
// modify the content shown based on the tab clicked (calls a function that has been passed in from elsewhere)
changeEventHandler(event);
}
};
// Add event listener to window object when component mounts
useEffect(() => {
window.addEventListener('click', handleWindowClick);
window.addEventListener('keydown', handleWindowKeydown);
// Remove event listener when component unmounts
return () => {
window.removeEventListener('click', handleWindowClick);
window.removeEventListener('keydown', handleWindowKeydown);
};
}, []);
Demo
Open the code inspector to see the active class applied to each button and its parent list item when clicked.
See the Pen Untitled by Kinsa Creative (@kinsaux) on CodePen.
Feedback?
Email us at enquiries@kinsa.cc.