Skip to content

Commit

Permalink
Rewrite table to functional component
Browse files Browse the repository at this point in the history
  • Loading branch information
Joozty committed May 22, 2020
1 parent 065ed6d commit 0f00b45
Show file tree
Hide file tree
Showing 14 changed files with 652 additions and 456 deletions.
32 changes: 32 additions & 0 deletions components/table/body.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import { Table } from '@oacore/design'

import TableRow from './row'
import NoDataFoundRow from './no-data-found-row'

const Body = React.memo(
({ handleRowClick, handleDoubleRowClick, data, isClickable, columns }) => (
<Table.Body onClick={handleRowClick} onDoubleClick={handleDoubleRowClick}>
{data === null && (
<Table.Row>
<Table.Cell colSpan={1000}>Loading data</Table.Cell>
</Table.Row>
)}

{data !== null &&
data.map((row, index) => {
const props = {
id: row.id,
index,
context: row,
columns,
isClickable,
}

return <TableRow key={row.id} {...props} />
})}
{data !== null && data.length === 0 && <NoDataFoundRow />}
</Table.Body>
)
)
export default Body
46 changes: 46 additions & 0 deletions components/table/footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'

import Pagination from './pagination'
import styles from './styles.module.css'

import { Button, Table } from 'design'

const Footer = React.memo(
({
children,
isFirstPageLoaded,
isLastPageLoaded,
totalLength,
fetchData,
size,
isLoading,
}) => (
<Table.Footer className={styles.footer}>
<Table.Row>
<Table.Cell colSpan={1000}>
<div className={styles.footerLeft}>{children}</div>
<div className={styles.footerRight}>
{isFirstPageLoaded && (
<Pagination
size={size}
total={totalLength}
isAllLoaded={isLastPageLoaded}
/>
)}
{!isLastPageLoaded && (
<Button
disabled={isLoading}
className={styles.loadNextPage}
onClick={() => fetchData({ next: true })}
>
Show more
</Button>
)}
</div>
</Table.Cell>
</Table.Row>
</Table.Footer>
)
)

export default Footer
28 changes: 28 additions & 0 deletions components/table/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import { Table } from '@oacore/design'

const Header = React.memo(
({ columns, handleColumnOrderChange, columnOrder }) => (
<Table.Head>
<Table.Row>
{columns.map(
({ props: { id, className: columnClassName, display } }) => (
<Table.HeadCell
key={id}
order={columnOrder[id]}
onClick={(event) => {
event.preventDefault()
handleColumnOrderChange(id)
}}
className={columnClassName}
>
{display}
</Table.HeadCell>
)
)}
</Table.Row>
</Table.Head>
)
)

export default Header
74 changes: 74 additions & 0 deletions components/table/hooks/use-dynamic-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useCallback, useReducer } from 'react'

const dataStateReducer = (state, action) => {
switch (action.type) {
case 'CHANGE_DATA_STATE':
return { ...state, ...(action.value || {}) }
default:
throw new Error('Wrong action type for dataStateReducer')
}
}

const useDynamicTableData = ({ pages, defaultSize = 100 }) => {
const [dataState, changeDataState] = useReducer(dataStateReducer, {
data: null,
size: defaultSize,
isLastPageLoaded: false,
totalLength: null,
})

const fetchData = useCallback(
async ({
next = false,
force = false,
columnOrder,
searchTerm,
selectedOption: type,
resetData = true,
} = {}) => {
let newSize = dataState.size

if (next) newSize += 100
else newSize = defaultSize

let newState = {}
if (force) {
pages.reset({
columnOrder,
searchTerm,
type,
})

newState = {
isLoading: true,
isLastPageLoaded: false,
}
}

newState.isLoading = true
if (resetData) newState.data = null

changeDataState({
type: 'CHANGE_DATA_STATE',
value: newState,
})

const newData = await pages.slice(0, newSize)

changeDataState({
type: 'CHANGE_DATA_STATE',
value: {
isLoading: false,
data: newData,
size: newSize,
isLastPageLoaded: pages.isLastPageLoaded,
totalLength: pages.totalLength,
},
})
},
[dataState.size]
)
return [dataState, fetchData]
}

export default useDynamicTableData
33 changes: 33 additions & 0 deletions components/table/hooks/use-table-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useMemo } from 'react'

import Column from '../column'
import Sidebar from '../sidebar'
import Action from '../action'

const useTableConfig = (children) =>
// recompute value only if children changes.
useMemo(() => {
const childrenArray = React.Children.toArray(children)

const columns = childrenArray.filter(
(child) =>
child.type === Column ||
Object.prototype.isPrototypeOf.call(Column, child.type)
)

const columnOrder = Object.fromEntries(
columns.map((column) => {
const { id, order = null } = column.props
return [id, order]
})
)

return {
sidebar: childrenArray.find((item) => item.type === Sidebar),
action: childrenArray.find((item) => item.type === Action),
columnOrder,
columns,
}
}, [children])

export default useTableConfig
142 changes: 142 additions & 0 deletions components/table/hooks/use-table-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useCallback, useReducer, useRef } from 'react'

import useDebouncedEffect from 'utils/hooks/use-debounced-effect'

const getNextOrder = (order) => {
if (order === 'asc') return 'desc'
if (order === 'desc') return 'asc'
return 'asc'
}

const changeOrder = ({ id, columnOrder }) => {
if (columnOrder[id] == null) return columnOrder

return Object.fromEntries(
Object.entries(columnOrder).map(([currId, currOrder]) => {
if (currId === id) return [currId, getNextOrder(currOrder)]
if (currOrder == null) return [currId, null]
return [currId, 'any']
})
)
}

const tableStateReducer = (state, action) => {
switch (action.type) {
case 'CLOSE_SIDEBAR':
return { ...state, expandedRowId: null }
case 'CHANGE_SELECTED_OPTION':
return { ...state, selectedOption: action.value }
case 'SEARCH_CHANGED':
return { ...state, searchTerm: action.value }
case 'CHANGE_TABLE_STATE':
return { ...state, ...(action.value || {}) }
case 'CHANGE_COLUMN_ORDER':
return {
...state,
columnOrder: changeOrder({
id: action.value,
columnOrder: state.columnOrder,
}),
}
default:
throw new Error('Invalid action type for Table')
}
}

const useTableState = ({ fetchDataProp, data, ...rest }) => {
const tableRowClickTimeout = useRef(null)
const [state, changeState] = useReducer(tableStateReducer, {
searchTerm: '',
expandedRowId: null,
selectedOption: '',
...rest,
})

// Event handlers
const handleSidebarClose = useCallback(
() => changeState({ type: 'CLOSE_SIDEBAR' }),
[]
)

const handleRowClick = useCallback(
(event) => {
if (event.detail === 1) {
const clickedRow = event.target.closest('tr')
tableRowClickTimeout.current = setTimeout(() => {
// ignore copy event
const selection = window.getSelection().toString()
if (selection.length) return

const rowId = clickedRow.dataset.id
changeState({
type: 'CHANGE_TABLE_STATE',
value: {
expandedRowId: data?.find((e) => e.id === rowId),
},
})
}, 200)
}
},
[data]
)

const handleDoubleRowClick = useCallback(() => {
// Don't expand row on double click. Let user to copy value
clearTimeout(tableRowClickTimeout.current)
}, [])

const handleSelectedOption = useCallback(
(value) =>
changeState({
type: 'CHANGE_SELECTED_OPTION',
value,
}),
[]
)

const handleColumnOrderChange = useCallback(
(value) =>
changeState({
type: 'CHANGE_COLUMN_ORDER',
value,
}),
[]
)

const handleSearchChange = useCallback(
({ target: { value } }) =>
changeState({
type: 'SEARCH_CHANGED',
value,
}),
[]
)

const { columnOrder, searchTerm, selectedOption } = state

const fetchData = (arg) =>
fetchDataProp({ ...arg, columnOrder, searchTerm, selectedOption })

useDebouncedEffect(() => {
fetchData({ force: true })
}, [searchTerm, selectedOption])

useDebouncedEffect(() => {
fetchData({ force: true, resetData: false })
}, [columnOrder])

return [
state,
{
handleSidebarClose,
handleRowClick,
handleDoubleRowClick,
handleSelectedOption,
handleColumnOrderChange,
handleSearchChange,
fetchData,
},
]
}

export default useTableState
Loading

0 comments on commit 0f00b45

Please sign in to comment.