Repository: zeit-ui/react (ts)

Issue:
Table.Column not updating label prop when it's updated.
<!-- Please do NOT DELETE the template. -->
<!-- No template issues will be closed. -->

# Bug report 🐞

## Version & Environment

  - Chrome latest
  - Version of `zeit-ui/react`: ^1.8.0-canary.0

## Expection

When using a table like this:

```
() => {
  const data = [
    { property: 'type', description: 'Content type', type: 'secondary | warning', default: '-' },
    { property: 'Component', description: 'DOM element to use', type: 'string', default: '-' },
    { property: 'bold', description: 'Bold style', type: 'boolean', default: 'true' },
  ]
  const [state, setState] = React.useState(0)
  React.useEffect(() => {
    const interval = setInterval(() => {
      setState((old) => old + 1)
    }, 1000)
    return () => {
      clearInterval(interval)
    }
  }, [])
  return (
    <Table data={data}>
      <Table.Column prop="property" label="property" />
      <Table.Column prop="description" label={`description: ${state}`} />
      <Table.Column prop="type" label="type" />
      <Table.Column prop="default" label="default" />
    </Table>
  )
}
```

The description is not being updated. This is a "minimal" reproducable example, I'm not doing exactly this in my application.

## Actual results (or Errors)

The description label isn't being updated, even though the variable `state` is being updated. 



Pull request description:
fix(table): fix column's props are not tracked
## Checklist

- [x] Fix linting errors
- [x] Tests have been added / updated (or snapshots)

## Change information

This change is to fix an error, but it may cause changes in user expectations, so it is only merged into branch *rc* at present.

fixed #359 

Patch:
diff --git a/components/table/table-column.tsx b/components/table/table-column.tsx
index b79a159..41acb39 100644
--- a/components/table/table-column.tsx
+++ b/components/table/table-column.tsx
@@ -16,19 +16,19 @@ const TableColumn: React.FC<React.PropsWithChildren<TableColumnProps>> = ({
   label,
   width,
 }) => {
-  const { appendColumn } = useTableContext()
+  const { updateColumn } = useTableContext()
   if (!prop || prop.trim() === '') {
     useWarning('The props "prop" is required.', 'Table.Column')
   }
 
   useEffect(() => {
-    appendColumn &&
-      appendColumn({
+    updateColumn &&
+      updateColumn({
         label: children || label,
         value: `${prop}`.trim(),
         width,
       })
-  }, [])
+  }, [label, prop, width])
 
   return null
 }
diff --git a/components/table/table-context.ts b/components/table/table-context.ts
index 26ec628..a01b926 100644
--- a/components/table/table-context.ts
+++ b/components/table/table-context.ts
@@ -8,7 +8,7 @@ export type TableColumnItem = {
 
 export interface TableConfig {
   columns: Array<TableColumnItem>
-  appendColumn?: (column: TableColumnItem) => void
+  updateColumn?: (column: TableColumnItem) => void
   removeRow?: (rowIndex: number) => void
 }
 
diff --git a/components/table/table.tsx b/components/table/table.tsx
index eeea224..0637448 100644
--- a/components/table/table.tsx
+++ b/components/table/table.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useRef } from 'react'
+import React, { useEffect, useMemo, useRef, useState } from 'react'
 import TableColumn from './table-column'
 import TableHead from './table-head'
 import TableBody from './table-body'
@@ -46,11 +46,17 @@ const Table: React.FC<React.PropsWithChildren<TableProps>> = ({
 }) => {
   const ref = useRef<HTMLTableElement>(null)
   const [{ width }, updateShape] = useRealShape<HTMLTableElement>(ref)
-  const [columns, setColumns, columnsRef] = useCurrentState<Array<TableColumnItem>>([])
+  const [columns, setColumns] = useState<Array<TableColumnItem>>([])
   const [selfData, setSelfData, dataRef] = useCurrentState<Array<TableColumnItem>>([])
-  const appendColumn = (column: TableColumnItem) => {
-    const pureCurrent = columnsRef.current.filter(item => item.value !== column.value)
-    setColumns([...pureCurrent, column])
+  const updateColumn = (column: TableColumnItem) => {
+    setColumns(last => {
+      const hasColumn = last.find(item => item.value === column.value)
+      if (!hasColumn) return [...last, column]
+      return last.map(item => {
+        if (item.value !== column.value) return item
+        return column
+      })
+    })
   }
   const removeRow = (rowIndex: number) => {
     const next = dataRef.current.filter((_, index) => index !== rowIndex)
@@ -61,7 +67,7 @@ const Table: React.FC<React.PropsWithChildren<TableProps>> = ({
   const initialValue = useMemo<TableConfig>(
     () => ({
       columns,
-      appendColumn,
+      updateColumn,
       removeRow,
     }),
     [columns],


Test patch:
diff --git a/components/table/__tests__/index.test.tsx b/components/table/__tests__/index.test.tsx
index 581f4cc..cfae28a 100644
--- a/components/table/__tests__/index.test.tsx
+++ b/components/table/__tests__/index.test.tsx
@@ -3,6 +3,7 @@ import { mount } from 'enzyme'
 import { Table, Code } from 'components'
 import { cellActions } from 'components/table/table-cell'
 import { nativeEvent, updateWrapper } from 'tests/utils'
+import { act } from 'react-dom/test-utils'
 
 const data = [
   { property: 'type', description: 'Content type', default: '-' },
@@ -166,4 +167,22 @@ describe('Table', () => {
     expect(wrapper.find('thead').find('code').length).not.toBe(0)
     expect(wrapper.html()).toMatchSnapshot()
   })
+
+  it('the changes of column should be tracked', () => {
+    const Mock = ({ label }: { label: string }) => {
+      return (
+        <Table data={data}>
+          <Table.Column prop="description" label={label} />
+        </Table>
+      )
+    }
+    const wrapper = mount(<Mock label="test1" />)
+    expect(wrapper.find('thead').find('tr').at(0).text()).toBe('test1')
+
+    act(() => {
+      wrapper.setProps({ label: 'test2' })
+    })
+    expect(wrapper.find('thead').find('tr').at(0).text()).toBe('test2')
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
 })