Created a Tabs component, but only problem is the previously selected Tab is not deactivating on clicking on new one.
Using Tailwind CSS to beautify elements
import clsx from 'clsx';
import {
SyntheticEvent,
ReactElement,
ComponentPropsWithoutRef,
memo,
Children,
cloneElement,
useState,
} from 'react';
export type TabProps = ComponentPropsWithoutRef<'li'> & {
label: string;
selected?: boolean;
disabled?: boolean;
onClick?: () => void;
};
export function Tab({
label,
selected,
disabled,
className,
onClick,
...props
}: TabProps) {
const [isActive, setIsActive] = useState(selected);
const handleClick = (event: SyntheticEvent<HTMLButtonElement>) => {
event.preventDefault();
console.log('Log 2');
if (disabled) return;
setIsActive(true);
onClick?.();
};
const selectedClassName =
isActive && !disabled ? 'border-b-2 border-black border-opacity-70' : '';
return (
<li
{...props}
className={clsx(
className,
'py-2 px-3',
selectedClassName,
disabled && 'text-gray-50',
)}
tabIndex={selected ? 0 : -1}
>
<button
type="button"
className={clsx(
'block text-lg',
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
)}
disabled={disabled}
onClick={handleClick}
>
{label}
</button>
</li>
);
}
export type TabsProps = ComponentPropsWithoutRef<'ul'> & {
selectedTab?: number;
children: ReactElement<TabProps> | ReactElement<TabProps>[];
onChange?: () => number;
};
function Tabs({ selectedTab = 0, children, ...props }: TabsProps) {
const [selectedTabIndex, setSelectedTabIndex] = useState(selectedTab);
const handleClick = (index: number) => {
console.log('Log 1');
setSelectedTabIndex(index);
props.onChange?.();
};
return (
<ul
{...props}
className={clsx(
props.className,
'flex list-none border-b-[1px] border-black border-opacity-10',
)}
>
{Children.map(children, (child, index) => {
const isSelected = index === selectedTabIndex;
console.log(index, child.props.className);
return cloneElement(child, {
key: index,
selected: isSelected,
onClick: () => handleClick(index),
});
})}
</ul>
);
}
export default memo(Tabs);
Calling components:
<Tabs>
<Tab label="Upcoming" />
<Tab label="In Progress" />
<Tab label="Completed" disabled />
</Tabs>
What I was doing wrong is setting and state for child Tab
component and not updating selected
prop coming from parent Tabs
.
That was really a silly mistake.
@GACy20 noticed the problem that helped me to fix this component.
Here's the fix:
import clsx from 'clsx';
import {
SyntheticEvent,
ReactElement,
ComponentPropsWithoutRef,
memo,
Children,
cloneElement,
useState,
} from 'react';
export type TabProps = ComponentPropsWithoutRef<'li'> & {
label: string;
selected?: boolean;
disabled?: boolean;
onClick?: () => void;
};
export function Tab({
label,
selected,
disabled,
className,
onClick,
...props
}: TabProps) {
const handleClick = (event: SyntheticEvent<HTMLButtonElement>) => {
event.preventDefault();
console.log('Log 2');
if (disabled) return;
selected = true;
onClick?.();
};
const selectedClassName =
selected && !disabled ? 'border-b-2 border-black border-opacity-70' : '';
return (
<li
{...props}
className={clsx(
className,
'py-2 px-3',
selectedClassName,
disabled && 'text-gray-50',
)}
tabIndex={selected ? 0 : -1}
>
<button
type="button"
className={clsx(
'block text-lg',
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
)}
disabled={disabled}
onClick={handleClick}
>
{label}
</button>
</li>
);
}
export type TabsProps = ComponentPropsWithoutRef<'ul'> & {
selectedTab?: number;
children: ReactElement<TabProps> | ReactElement<TabProps>[];
onChange?: (activeTab: number) => void;
};
function Tabs({ selectedTab = 0, children, ...props }: TabsProps) {
const [selectedTabIndex, setSelectedTabIndex] = useState(selectedTab);
const handleClick = (index: number) => {
console.log('Log 1');
setSelectedTabIndex(index);
props.onChange?.(selectedTabIndex);
};
return (
<ul
{...props}
className={clsx(
props.className,
'flex list-none border-b-[1px] border-black border-opacity-10',
)}
>
{Children.map(children, (child, index) => {
const isSelected = index === selectedTabIndex;
console.log(index, child.props.className);
return cloneElement(child, {
key: index,
selected: isSelected,
onClick: () => handleClick(index),
});
})}
</ul>
);
}
export default memo(Tabs);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.