简体   繁体   中英

TypeScript React : Call from child component is not working

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM