datatables.net-react 1.0.0 slots function does not consider all columns
datatables.net-react 1.0.0 slots function does not consider all columns
Description of problem: The use of columnDefs
with the string _all
- all columns (i.e. assign a default) doesn't work for datatables.net-react slots, in the source code, the check for string _all
is missing.
It seems that the datatables.net-react repo: https://github.com/DataTables/React is not visible, so I'll post my fix in here.
in the source code:
function applySlots(cache: SlotCache, options: DTConfig, slots: DataTableSlots) {
if (!options.columnDefs) {
options.columnDefs = [];
}
Object.keys(slots).forEach((name) => {
let slot = slots[name];
if (!slot) {
return;
}
// Simple column index
if (name.match(/^\d+$/)) {
// Note that unshift is used to make sure that this property is
// applied in DataTables _after_ the end user's own options, if
// they've provided any.
options.columnDefs!.unshift({
target: parseInt(name),
render: slotRenderer(cache, slot)
});
}
else {
// Column name
options.columnDefs!.unshift({
target: name + ':name',
render: slotRenderer(cache, slot)
});
}
});
}
the fix:
function applySlots(cache: SlotCache, options: DTConfig, slots: DataTableSlots) {
if (!options.columnDefs) {
options.columnDefs = [];
}
Object.keys(slots).forEach((name) => {
let slot = slots[name];
if (!slot) {
return;
}
// Apply to all columns
if (name === '_all') {
options.columnDefs!.unshift({
targets: '_all',
render: slotRenderer(cache, slot)
});
}
// Simple column index
else if (name.match(/^\d+$/)) {
// Note that unshift is used to make sure that this property is
// applied in DataTables _after_ the end user's own options, if
// they've provided any.
options.columnDefs!.unshift({
target: parseInt(name),
render: slotRenderer(cache, slot)
});
}
else {
// Column name
options.columnDefs!.unshift({
target: name + ':name',
render: slotRenderer(cache, slot)
});
}
});
}
with this fix, slots like below can work.
slots={{
_all: (data, type, row) => {
return "<div class='whitespace-normal max-w-60'>" + data || "-" + "</div>";
}
}}
which now applies to all columns
Hope it helps!
Edit: Oh! This is not perfect and the _all will overwrite other columns!
Replies
Also another performance issue is about the render of React component using slots. It seems that the deferRender is not applied for the slots.
If I have more than 30 rows (with a button (React component) in column 0, rendered with slots), I can see a slightly delay. And noticeable delay when over 80 rows. And my Chrome crashes when I have 20000 rows. But I do need to render those buttons for some interactions.
Any advice on how to solve this performance issue? I have deferRender and pagination enabled. But it seems that the slots are making React render all the rows, causing a significant performance issue.
https://stackblitz.com/edit/datatables-net-react-simple-pm4amf?file=src%2FApp.tsx
This is the demo case using slots to render button.
Try to feel the delay from 20, 200, 2000, and 20000 rows. it took me ~5 seconds to see the result for 2000 rows.
Doh - sorry about that. I had it private during the initial development of the component and then must have forgotten to update it. Don enow.
Good point about the
_all
- thank you - I'll get that included.Performance-wise - try only to return JSX for the
display
type (second parameter to the rendering function). If you return JSX for all, you are forcing it to render that JSX and then extract the data needed. For type detection, sorting and filtering that normally is not needed and you can just return the underlying data.The
display
type will only be requested when DataTables needs to draw the cell on the page, so with pagination enabled, you should hopefully see a decent difference in performance.Allan
Thank you Allan for pointing out that! I just tried to wrap the JSX for only display type.
it will just run for only the first page indeed.
But then, when I resize the page. It continues to render the rest on init! And then the whole page freezes for a moment.
See the demo case below.
https://stackblitz.com/edit/datatables-net-react-simple-e5jf2a?file=src%2FApp.tsx
Open the dev tool first, and then reload the preview. See that only 10 rows are rendered and print
display
in console. Resize the page (or click the column to sort), see that all the rest rows are being rendered and printdisplay
in console.In my case since I have sidebar so I must call
columns.adjust()
right after init, which means the all JSX components are being rendered actually. And that causes the performance issue.Is it possible to stop rendering all rows when sorting or resizing? That is, when resizing, only the current rows on the first page are taken into account for the draw. And when sorting, sort the orthogonal data first and then render the JSX in the cells for the first page.
There isn't an option for that. What I think must be happening is that it is trying to find the longest string to optimise the column widths. I'll need to have a look into finding a way to either limit that to the current page, or some other option.
Allan
That's it!
Disable the
autoWidth
works! But then the columns are not automatically adjusted when the window size is changed.I use
draw
andorder
to adjust the column instead.See here:
https://stackblitz.com/edit/datatables-net-react-simple-ijtxhg?file=src%2FApp.tsx
I think this would be fine for me to disable
autoWidth
as I must call thecolumns.adjust()
anyway later on.This one listens to the window resize event:
https://stackblitz.com/edit/datatables-net-react-simple-trcv79?file=src%2FApp.tsx
It would be nice if there were an option (or built-in default) for optimization, as you mentioned.
Cunning! That hadn't crossed my mind, but good to hear you found a workaround for now. Yes, it would definitely be good to improve this aspect of the software.
Allan
Hi Allan, I just realized that the slots doesn't have
meta
argument.From https://datatables.net/reference/option/columns.render
There is 4 arguments
But for the slots described in https://datatables.net/manual/react#React-Components:
I tried to get the row index while rendering but the
meta
doesn't exist for the slots.I wasn't sure about adding the meta property - the main use of it was to get the rendered cell node, but that is redundant in React. I can certainly add it in though. What's the use case?
Allan
For example I use Scroller plug-in and slots to render button in cells.
When I click the button, I want to add its row index in the onClick event so that I can replace the classes of the button to change its color to indicate that it is selected. However, since it does not update itself when the state/class changes, I have to use
cell().data()
andcell().invalidate()
to manually update the button when I click it I guess.And another use case would be restoring:
since I create the table in a Dialog (Modal), I would open it again later and use
row().scrollTo()
to scroll the table to the selected button, so that the selected button is visible/findable when it opens. Especially if the table is unmounted, we can restore the state later using the row index.I'm not sure if I'm overthinking whether or not they need a row index to do that.
Would:
do the job for you? Where
this
is the button (modify as needed). I generally try to steer people away from the meta property. I don't have a good solid reason, but it just always felt like a bit of a workaround.Allan
Hi Allan, it works for the onClick event. Thank you!
I use:
But I'm a bit stuck again. How can I do to change the
variant
prop value of the Button based on the selectedIndex?I want to change the variant value from
outline
todefault
(which will render to another button style) if the button's index euqals to the selected indexI will then update it with
useEffect
:any workarounds without using the meta property?
BTW, I noticed that the
row().scrollTo()
does not scroll exactly the row indexposition
. I suspect it is due to the rendered button that causes the changing in the height that I mentioned in another discussion's comment in here. I will try to provide a test case if needed.In general, I guess it would be the
scroller.rowHeight
that is not fixed so the height is changingHere's the test case about the Scroller
row().scrollTo()
with slots render issue:https://stackblitz.com/edit/datatables-net-react-simple-fvmt9f?file=src%2FApp.tsx
It will have slightly shift after using
row().scrollTo()
. And even more shift when I don't provide a fixed height using:I don't think so I'm afraid. Not in this case since it is happening inside the renderer rather than externally in an event handler. I'll see about getting that change in next week.
Regarding the scrolling issue - you might need to call
scroller.measure()
when the table is displayed.I do have a plan to have resizing calculations happen automatically - probably DataTables 2.2 when that happens.
Allan
It seems that it cannot scroll to the last cell correctly.
For the clicked cell in the middle, the scroll position seems to be different each time the table is opened, even if the
scroller.measure()
is used.Here's the test case: https://stackblitz.com/edit/datatables-net-react-simple-yq8xph?file=src%2FApp.tsx
Thank you - I'll take a look at it when I'm back in the office.
Allan
Hi Allan,
Is there any updates about getting the
meta
property available for slot for React?Regarding the issue with the scroller, below is another post that may relate to the scroller scroll shift issue:
https://datatables.net/forums/discussion/80158/scroller-plug-in-breaks-info-configuration
Apologies - not yet. I've been focusing on some changes for Editor. React updates will be the first thing I do once that work is complete.
Allan
I've made a PR to support the
meta
argument.Below you can see the result after applying the fix, which fulfills what I wanted to achieve.
There is just one small thing left: after clicking on the button, it actually flickers:
Because I have to use:
table.current?.dt().rows().invalidate().draw(false);
to allow the buttons to re-render the styles.
CPU 20x slowdown:
So the rows will be blank during re-rendering.
This is exactly where the scroller gets shifted. The workaround for the shifting issue is to apply a fixed height for all rows to maintain the height value.
Update: By recording the previously clicked index and the currently clicked index, I now only have to re-render two buttons. Much more efficient, especially with lots of rows.
I'm not familiar with React and may not understand your solution. I don't understand why you need to use the invalidate APIs for changes to the button's attributes. My experience is Datatables keeps the
cell().node()
, for example, updated with the changes to the attribute changes.I experimented a bit here:
https://live.datatables.net/nadedumi/1/edit
The
variant
attribute changes stay with the HTML output and with `-api cell().node(). This can also be seen with the class names. Click the button and both are changed. Go to another page then back. The changes are properly reflected.However the Datatables data cache is not updated. For this you will need to invalidate. Uncomment
table.cell( td ).invalidate();
to see the effect. Do you need this just for attribute changes?If you need to use invalidation you might not need to use
draw()
. Callingdraw()
will update the searching, sorting, etc of the Datatable. Likely you won't need to updated searching and sorting when just changing the attributes. Try removing the chaineddraw()
.Again I may be missing something with your solution.
Kevin
@kthorngren the
variant
attribute is theprops
in the React component that I used to define the class names for the<Button>
component (because I use Taildwind CSS).The button is rendered by React and DT is unaware of the changes to the
variant
string. Therefore, I have to invalidate the row. This will force a re-render of the row and the style will be applied according to thevariant
value.BUT thank you for pointing out the unnecessary use of
draw
!You are absolutely right!
row().invalidate()
orcell().invalidate()
is enough to see the class name changes!Here's a stackblitz example I made that you can play with.
Interesting. I probably should learn React and see how it behaves with Datatables. Thanks for the test case. I see the issue when invalidate is not used.
Glad that worked out.
Kevin
BTW, replace
useEffect
withuseLayoutEffect
will look better visually, as the user will not see the blank row before the rerender is finished.