From 98f701ec3561a76ea65c08cf3cfd7417821983e3 Mon Sep 17 00:00:00 2001 From: witt Date: Wed, 15 Jul 2020 18:02:07 +0800 Subject: [PATCH] feat(tabs): sync the label and set value to required (#334) * feat(tabs): sync the label and set value to required * test(tabs): add testcase for label sync * docs(tabs): update value to required --- components/tabs/__tests__/index.test.tsx | 48 ++++++++++-------------- components/tabs/tabs-context.ts | 1 - components/tabs/tabs-item.tsx | 12 ++---- components/tabs/tabs.tsx | 30 +++++++-------- pages/en-us/components/tabs.mdx | 2 +- pages/zh-cn/components/tabs.mdx | 2 +- 6 files changed, 40 insertions(+), 55 deletions(-) diff --git a/components/tabs/__tests__/index.test.tsx b/components/tabs/__tests__/index.test.tsx index 102711a..aa23de6 100644 --- a/components/tabs/__tests__/index.test.tsx +++ b/components/tabs/__tests__/index.test.tsx @@ -87,36 +87,26 @@ describe('Tabs', () => { expect(active.text()).toContain('label2') }) - it('should warning when label duplicated', () => { - const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - mount( - - - test-1 - - - test-2 - - , - ) - expect(errorSpy).toHaveBeenCalled() - errorSpy.mockRestore() - }) + it('should re-render when items updated', () => { + const Mock = ({ label = 'label1' }) => { + return ( + + + test-1 + + + test-label-fixed + + + ) + } + const wrapper = mount() + let active = wrapper.find('header').find('.active') + expect(active.text()).toContain('label1') - it('should use label as key when value is missing', async () => { - const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - const wrapper = mount( - - test-1 - test-2 - , - ) - expect(errorSpy).not.toHaveBeenCalled() - - wrapper.setProps({ value: 'label2' }) - await updateWrapper(wrapper, 350) - const active = wrapper.find('header').find('.active') + wrapper.setProps({ label: 'label2' }) + active = wrapper.find('header').find('.active') expect(active.text()).toContain('label2') - errorSpy.mockRestore() + expect(() => wrapper.unmount()).not.toThrow() }) }) diff --git a/components/tabs/tabs-context.ts b/components/tabs/tabs-context.ts index f9150c2..19a9fd8 100644 --- a/components/tabs/tabs-context.ts +++ b/components/tabs/tabs-context.ts @@ -8,7 +8,6 @@ export interface TabsLabelItem { export interface TabsConfig { register?: (item: TabsLabelItem) => void - unregister?: (item: TabsLabelItem) => void currentValue?: string inGroup: boolean } diff --git a/components/tabs/tabs-item.tsx b/components/tabs/tabs-item.tsx index 30f18d3..e128b5d 100644 --- a/components/tabs/tabs-item.tsx +++ b/components/tabs/tabs-item.tsx @@ -4,7 +4,7 @@ import { useTabsContext } from './tabs-context' interface Props { label: string | React.ReactNode - value?: string + value: string disabled?: boolean } @@ -16,20 +16,16 @@ export type TabsItemProps = Props & typeof defaultProps const TabsItem: React.FC> = ({ children, - value: userCustomValue, + value, label, disabled, }) => { - const value = useMemo(() => userCustomValue || `${label}`, [userCustomValue, label]) - const { register, unregister, currentValue } = useTabsContext() + const { register, currentValue } = useTabsContext() const isActive = useMemo(() => currentValue === value, [currentValue, value]) useEffect(() => { register && register({ value, label, disabled }) - return () => { - unregister && unregister({ value, label, disabled }) - } - }, []) + }, [value, label, disabled]) /* eslint-disable react/jsx-no-useless-fragment */ return isActive ? <>{children} : null diff --git a/components/tabs/tabs.tsx b/components/tabs/tabs.tsx index f00f053..89d900f 100644 --- a/components/tabs/tabs.tsx +++ b/components/tabs/tabs.tsx @@ -2,8 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react' import TabsItem from './tabs-item' import useTheme from '../styles/use-theme' import { TabsLabelItem, TabsConfig, TabsContext } from './tabs-context' -import useCurrentState from '../utils/use-current-state' -import useWarning from '../utils/use-warning' interface Props { initialValue?: string @@ -32,24 +30,26 @@ const Tabs: React.FC> = ({ }) => { const theme = useTheme() const [selfValue, setSelfValue] = useState(userCustomInitialValue) - const [tabs, setTabs, tabsRef] = useCurrentState>([]) + const [tabs, setTabs] = useState>([]) const register = (next: TabsLabelItem) => { - const hasItem = tabsRef.current.find(item => item.value === next.value) - if (hasItem) { - useWarning('The "value" of each "Tabs.Item" must be unique.', 'Tabs') - } - setTabs([...tabsRef.current, next]) - } - const unregister = (next: TabsLabelItem) => { - const nextTabs = tabsRef.current.filter(item => item.value !== next.value) - setTabs([...nextTabs]) + setTabs(last => { + const hasItem = last.find(item => item.value === next.value) + if (!hasItem) return [...last, next] + return last.map(item => { + if (item.value !== next.value) return item + return { + ...item, + label: next.label, + disabled: next.disabled, + } + }) + }) } const initialValue = useMemo( () => ({ register, - unregister, currentValue: selfValue, inGroup: true, }), @@ -71,13 +71,13 @@ const Tabs: React.FC> = ({
- {tabs.map((item, index) => ( + {tabs.map(item => (
clickHandler(item)}> {item.label}
diff --git a/pages/en-us/components/tabs.mdx b/pages/en-us/components/tabs.mdx index 60f1cb1..c1fef3f 100644 --- a/pages/en-us/components/tabs.mdx +++ b/pages/en-us/components/tabs.mdx @@ -134,7 +134,7 @@ Display tab content. | Attribute | Description | Type | Accepted values | Default | | ------------------- | ------------------- | --------- | --------------- | ------- | | **label**(required) | display tab's label | `string` | - | - | -| **value** | unique ident value | `string` | - | - | +| **value**(required) | unique ident value | `string` | - | - | | **disabled** | disable current tab | `boolean` | - | `false` | useTabs diff --git a/pages/zh-cn/components/tabs.mdx b/pages/zh-cn/components/tabs.mdx index 59933ce..30143de 100644 --- a/pages/zh-cn/components/tabs.mdx +++ b/pages/zh-cn/components/tabs.mdx @@ -131,7 +131,7 @@ export const meta = { | 属性 | 描述 | 类型 | 推荐值 | 默认 | | ----------------- | -------------- | --------- | ------ | ------- | | **label**(必须的) | 选项卡标签文字 | `string` | - | - | -| **value** | 唯一鉴别值 | `string` | - | - | +| **value**(必须的) | 唯一鉴别值 | `string` | - | - | | **disabled** | 禁用当前选项卡 | `boolean` | - | `false` | useTabs