T
TanStack11mo ago
fair-rose

How to make defaultColumn context aware and conditionally add functionality

Hey there, I'm writing an invoice app for video productions and for that I make extensive use of the TanStack table. As for the rows I have different item types like "title", "item", "subtotal", … and each type has it's own specialities and columns to it. The tricky part I'm challenged with is that most of the cells have one thing in common: They're inline editable. This I have implemented using the defaultColumn. What I'm struggling with is how to take this defaultColumn as a base and add features and such on top of it. To give you an example: A title item for instance does not need parameters like "quantity", "unit" or "price" but should be span across almost the entire row. In order to make that working I have to conditionally check for each column if the row type is an item or a title and also check if that type needs to have the current column with an editable input or if an empty cell should be returned. And depending on the type of column I need different input formatting. A title should have bold text, number fields should render differently and text-align on the right and if the input contains currency I want it to have two decimals and the currency added as well. This results in a lot of spaghetti code and endless if-else-conditions and I feel like there must be an easier way to solve this. So I'd be very happy if someone can push me into the right direction on how to make this more versatile and easier to manage. Ideally I was thinking if there's a way to define the base in the columnHelper and then take the defaultColumn (to keep it DRY) and add it on top for rendering. I'm not sure on how to get this working becasue as soon as I define cell it's not using defaultColumn for rendering. Or is meta what I should be using for in this case? Any thoughts on this? Thanks a ton for your help and time. Much appreciated! 🙌🏼
No description
1 Reply
fair-rose
fair-roseOP11mo ago
Here's how I define a column for instance
columnHelper.accessor('quantity', {
header: () => h('div', { class: 'text-right' }, 'Quantity'),
meta: ({ row }) => {
const hideItem =
row.original.itemType === 'ITEM' && row.original.unit === 'fixed' ? true : false
console.log('row: ', row, hideItem)
return {
hideItem,
}
},
size: 70,
// can't define cell here otherwise defaultColumn won't be rendered
}),
columnHelper.accessor('quantity', {
header: () => h('div', { class: 'text-right' }, 'Quantity'),
meta: ({ row }) => {
const hideItem =
row.original.itemType === 'ITEM' && row.original.unit === 'fixed' ? true : false
console.log('row: ', row, hideItem)
return {
hideItem,
}
},
size: 70,
// can't define cell here otherwise defaultColumn won't be rendered
}),
And here's an example of the spaghetti code and lots of conditional renderings.
const defaultColumn: Partial<ColumnDef<ItemWithComputedValues>> = {
cell: ({ getValue, row: { original }, column: { id, columnDef }, table }) => {

// Clear certain cells conditionally
if (id === 'quantity' && original.unit === 'fixed') {
return h('div', { class: '' })
}
if (original.itemType === 'TITLE' || original.itemType === 'SUBTOTAL') {
if (
id !== 'name' &&
id !== 'netTotal' &&
id !== 'costNet' &&
id !== 'costNetTotal' &&
id !== 'profit'
) {
return
}
}
let initialValue = getValue()?.toString() ?? columnDef?.meta?.placeholder ?? ''
if (
id === 'quantity' ||
id === 'totalCount' ||
id === 'unitPrice' ||
id === 'costNet' ||
id === 'costNetTotal' ||
id === 'profit'
) {
// Format number to max 2 decimal places or remove decimals if no decimals needed
initialValue = Number(original[id])
.toFixed(2)
.replace(/\.0*$|(\.\d*[1-9])0+$/, '$1')
}

// Return editable cell
return h(Editable, {
defaultValue: initialValue,
autoresize: true,
onSubmit: (value) => {
if (value !== initialValue) {
table.options.meta?.updateData(original.id, id, value)
}
},
width: id === 'name' ? 'full' : '',
placeholder: placeholder ?? '0',
class: [original.itemType === 'TITLE' ? 'capitalize font-bold' : ''],
autoResize: false,
submitMode: 'both',
align: id === 'name' ? 'left' : 'right',
disabled: editTable.value ? false : true,
variant: ghostMode.value ? 'ghost' : 'default',
})
},
}
const defaultColumn: Partial<ColumnDef<ItemWithComputedValues>> = {
cell: ({ getValue, row: { original }, column: { id, columnDef }, table }) => {

// Clear certain cells conditionally
if (id === 'quantity' && original.unit === 'fixed') {
return h('div', { class: '' })
}
if (original.itemType === 'TITLE' || original.itemType === 'SUBTOTAL') {
if (
id !== 'name' &&
id !== 'netTotal' &&
id !== 'costNet' &&
id !== 'costNetTotal' &&
id !== 'profit'
) {
return
}
}
let initialValue = getValue()?.toString() ?? columnDef?.meta?.placeholder ?? ''
if (
id === 'quantity' ||
id === 'totalCount' ||
id === 'unitPrice' ||
id === 'costNet' ||
id === 'costNetTotal' ||
id === 'profit'
) {
// Format number to max 2 decimal places or remove decimals if no decimals needed
initialValue = Number(original[id])
.toFixed(2)
.replace(/\.0*$|(\.\d*[1-9])0+$/, '$1')
}

// Return editable cell
return h(Editable, {
defaultValue: initialValue,
autoresize: true,
onSubmit: (value) => {
if (value !== initialValue) {
table.options.meta?.updateData(original.id, id, value)
}
},
width: id === 'name' ? 'full' : '',
placeholder: placeholder ?? '0',
class: [original.itemType === 'TITLE' ? 'capitalize font-bold' : ''],
autoResize: false,
submitMode: 'both',
align: id === 'name' ? 'left' : 'right',
disabled: editTable.value ? false : true,
variant: ghostMode.value ? 'ghost' : 'default',
})
},
}

Did you find this page helpful?