React table v8 - how to render custom cell content? - react-table

I'm using #tanstack/react-table v8, and I want to have some icons and buttons in one of my table row cells. Looking at the docs I can use a display column for this:
Display columns do not have a data model which means they cannot be sorted, filtered, etc, but they can be used to display arbitrary content in the table, eg. a row actions button, checkbox, expander, etc.
The docs also mention this about custom rendering of cell content:
By default, columns cells will display their data model value as a string. You can override this behavior by providing custom rendering implementations. ... You can provide a custom cell formatter by passing a function to the cell property ...
The docs show this example for custom rendering of a cell:
columnHelper.accessor('firstName', {
cell: props => <span>{props.getValue().toUpperCase()}</span>,
})
So, I have defined a display column using ColumnHelper:
columnHelper.display({
id: "actions",
cell: (props) => (
<div className="w-10 h-10 bg-red">
<span>actions?</span>
</div>
),
}),
How do I access this render function when creating the table? I'm creating the table body as follows (the table variable is returned by the useReactTable hook):
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr className={classNames(index % 2 !== 0 && "bg-tableRowGray")}>
{row.getAllCells().map((cell) => (
<td>{cell.getValue()}</td>
))}
</tr>
))}
</tbody>
cell.getValue() and cell.renderValue() do not call the custom render function and I don't see any other render functions on the cell object. Am I just overlooking something really simple here?

Turns out I was overlooking something simple. #tanstack/react-table exports a flexRender function which is used to render the cell, e.g.
import {flexRender} from "#tanstack/react-table";
...
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr className={classNames(index % 2 !== 0 && "bg-tableRowGray")}>
{row.getAllCells().map((cell) => (<td>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>))}
</tr>
))}
</tbody>

Related

React conditional class name setting for styling

I am trying to make a quiz app using React.
I am currently working on the main quiz page where I have 4 buttons, and each of the buttons denotes an answer I'm importing from a question bank.
I want the current selected button to be highlighted, and for this I am currently using a state for each button. Is there any way to just use one state and deal with all four of the buttons, as this way is too tedious and cannot be used for a large number of such buttons? Also, I want only one button, the one the user selects finally, to be highlighted. So for this reason I need to set the state of all the other buttons to null, which makes the task even more tedious.
Here is the div containing the buttons
<div>
<button className={selected1} onClick={() => dealingWithOptions("A")}>{questions[currentQuestion].optionA}</button>
<button className={selected2} onClick={() => dealingWithOptions("B")}>{questions[currentQuestion].optionB}</button>
<button className={selected3} onClick={() => dealingWithOptions("C")}>{questions[currentQuestion].optionC}</button>
<button className={selected4} onClick={() => dealingWithOptions("D")}>{questions[currentQuestion].optionD}</button>
</div>
Here is the function dealing with the options clicking
const [selected1,setSelectedButton1] = useState("")
const [selected2,setSelectedButton2] = useState("")
const dealingWithOptions = (op) => {
setOptionChosen(op);
if (op=="A") {
setSelectedButton1("selected1");
setSelectedButton2("")
setSelectedButton3("")
setSelectedButton4("")
} else if (op=='B') {
setSelectedButton1("");
setSelectedButton2("selected2")
setSelectedButton3("")
setSelectedButton4("")
} else if (op=='C') {
setSelectedButton1("");
setSelectedButton2("")
setSelectedButton3("selected3")
setSelectedButton4("");
}
else if (op=='D') {
setSelectedButton1("");
setSelectedButton2("")
setSelectedButton3("")
setSelectedButton4("selected3");
}
}
It can be solved and optimized in many ways. I am trying to give what suit your current code most.
I assume you have a state that stores choosen option.
Now update all the button like this
<button className={choosen == "A" ? "selected" : "" } onClick={() => dealingWithOptions("A")}>{questions[currentQuestion].optionA}</button>
Here choosen is the state where the selected option is being stored.
Explanation: Here what we are doing is, we are matching for each button that if it is the selected button then add the selected class else add nothing.
I would suggest having one state, that stores the chosen option (so either A, B, C or D) and then in your JSX part you have a condition that assigns the classname "selected" to the appropiate button.
If the selection changes so will the state, which triggers a rerender (so you don't even have to take away the "selected" class, since it will just be assinged to (and only) the right one on rerender
I'd suggest you set one piece of state to maintain the selected button.
setSelectedButton('A')
Or undefined if none is selected
Then,
<button className={selectedButton === 'A' ? 'selected1' : ''}>...
That said for conditional classes I'd use something like clsx rather than building class strings manually
e.g.
<button className={clsx({selected1: selectedButton === 'A'})}>...
Several ways to achieve this. You can set one state: const [selectedId, setSelectedId] = useState("")
Define the function: const selectHandler = (e) => setSelectedId(e.target.name)
On your buttons call the function and set the state to the Id: <button className={selectedId === "a" && "selected"} name="a" onClick={() => setSelectedId("a")}>Button A</button> For the rest of the buttons, change the name as well as the parameter passed to the function. Also the string inside the className comparison

How to change style of an element in React without using querySelector?

I am new to the React. Here, I'm trying to override the CSS of a class based on a condition
let customizeColumn = document.querySelector(".main-table-body");
!expandFilter ? customizeColumn.setAttribute("style", `height:calc(100vh - 345px)`) : customizeColumn.setAttribute("style", `height:calc(100vh - 302px)`);
Here, I am managing the state of this variable and I am calling toggleFilter on the button click
const [expandFilter, toggleFilter] = useState(false);
But I don't want to use document.querySelector(".main-table-body") to fetch the class, Is there any other way of doing this. I am using Material-UI too. If that helps
In the render of the main-table-body, check the state to determine what CSS property it should have, eg:
return (
<table
class="main-table-body"
style={{ height: `calc(100vh - ${expandFilter ? 302 : 345}px)` }}
>
// table contents
</table>
);
You can apply style using style attribute.
In the render method do like this
const pixelsToReduce= expandFilter ? 302 : 345
const height=`calc(100vh - pixelsToReduce)`
return (
<table
class="main-table-body"
style={{height:height}}
>
</table>
)

React table - rearranging - group by keep position of column

I'm using the useGroupBy hook to group my react-table table. However when grouping the default behaviour is to group re-arrange the columns in the table but I wan't to keep them in the same position, do you know how to do this with the optional useGroupByHook?
Here is the codesandbox: https://codesandbox.io/s/ecstatic-tree-bq19p?file=/src/App.js
When pressing groupby i.e age it will move age to the left..
Before grouping:
You can fix the ordering during table rendering:
const fixColumnOrder = (cells) => columns.map(column => cells.find(cell => cell.column.id === column.id))
<tr {...row.getRowProps()}>
{fixColumnOrder(row.cells).map((cell) => {
return (
<td {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
)
})}
</tr>
You need to do the same trick for the headers

Change a block type in InnerBlocks template without losing content

I have a block type that contains other blocks using InnerBlocks, with the output generated by an InnerBlocks template (similar to the columns core block). I want to be able to change one of the blocks to another block type, (one that has the transform defined), but when I do, the content gets erased.
Is there a way to do this?
By way of analogy, if the core columns block had sub-blocks "column-third" and "column-half", would there be a way of changing from two "column-half"s to three "column-third"s without losing the data?
To expand on the above analogy, suppose I made clones of the core columns and column blocks, and I modify the columns block like so:
The template generation function:
var childBlockName = 'core/column';
const getColumnsTemplate = memoize( ( columns ) => {
return times( columns, () => [ childBlockName ] );
} );
and the RangeControl in the edit function:
<RangeControl
label={ __( 'Columns' ) }
value={ columns }
onChange={ ( nextColumns ) => {
childBlockName = 'coreclone/column';
setAttributes( {
columns: nextColumns,
} );
} }
min={ 2 }
max={ 6 }
/>
core/column and coreclone/column are identical. In this example, when I change the number of columns, the child block-type changes in the template. The core/column blocks don't get converted to coreclone/column blocks, they just get wiped and new coreclone/column blocks are made. I would like them to be transformed to coreclone/column blocks without the contents being wiped.

Grid only displaying one Column

I've recently started trying to use react-virtualized, but I've ran into an issue from the get-go. I've been trying to build a very simple grid, at first I loaded my data in and it wasn't working properly, but I've changed it to a simple 4x4 Grid and it's still giving me issues. Right now all 16 cells are being loading in a single column, and I've tried logging the rowIndex and the columnIndex, and those are giving me the correct output.
I'm not sure if I'm doing something wrong when I call the Grid, or if I'm doing something wrong with the cellRenderer, I would really appreciate some help with this. I have parts of my code down below.
_cellRenderer({columnIndex, key, rowIndex, styles}){
return(
<div>
{columnIndex}
</div>);
}
render(){
return(
<div>
<Autosizer>
{({width}) => (
<Grid
cellRenderer={this._cellRenderer}
columnCount={4}
columnWidth={30}
rowCount={4}
rowHeight={30}
width={400}
height={400}
/>
)}
</Autosizer>
</div>
);
}
You aren't using the parameters passed to your renderer. For example, style and key are both passed for a reason and you must use them. (The documentation should make this pretty clear.)
Put another way:
// Wrong:
function cellRenderer ({ columnIndex, key, rowIndex, style }) {
return (
<div>
{columnIndex}
</div>
)
}
​
// Right:
function cellRenderer ({ columnIndex, key, rowIndex, style }) {
return (
<div
key={key}
style={style}
>
{columnIndex}
</div>
)
}
Also, in case you didn't notice, the parameter is style and not styles like your code snippet shows.

Resources