The table state is lost during a re-render. All filters, page-rows, and other related information are reset.
Example to re-produce the issue:
import React from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import {
createMuiTheme,
MuiThemeProvider,
withStyles
} from "@material-ui/core/styles";
class App extends React.Component {
constructor(props) {
super(props);
window.table = this;
}
getMuiTheme = () =>
createMuiTheme({
overrides: {
MUIDataTable: {
root: {
backgroundColor: "#FF000"
},
paper: {
boxShadow: "none"
}
},
MUIDataTableBodyCell: {
root: {
backgroundColor: "#FF0000"
}
}
}
});
render() {
const columns = [
{
name: "Name",
options: {
filter: true
}
},
{
name: "Title",
options: {
filter: true
}
},
{
name: "Location",
options: {
filter: false
}
},
{
name: "Age",
options: {
filter: true
}
},
{
name: "Salary",
options: {
filter: true,
sort: false
}
}
];
const data = [
["Gabby George", "Business Analyst", "Minneapolis", 30, 100000],
["Aiden Lloyd", "Business Consultant", "Dallas", 55, 200000],
["Jaden Collins", "Attorney", "Santa Ana", 27, 500000],
["Franky Rees", "Business Analyst", "St. Petersburg", 22, 50000],
["Aaren Rose", "Business Consultant", "Toledo", 28, 75000],
["Blake Duncan", "Business Management Analyst", "San Diego", 65, 94000],
["Frankie Parry", "Agency Legal Counsel", "Jacksonville", 71, 210000],
["Lane Wilson", "Commercial Specialist", "Omaha", 19, 65000],
["Robin Duncan", "Business Analyst", "Los Angeles", 20, 77000],
["Mel Brooks", "Business Consultant", "Oklahoma City", 37, 135000],
["Harper White", "Attorney", "Pittsburgh", 52, 420000],
["Kris Humphrey", "Agency Legal Counsel", "Laredo", 30, 150000],
["Frankie Long", "Industrial Analyst", "Austin", 31, 170000],
["Brynn Robbins", "Business Analyst", "Norfolk", 22, 90000],
["Justice Mann", "Business Consultant", "Chicago", 24, 133000],
[
"Addison Navarro",
"Business Management Analyst",
"New York",
50,
295000
],
["Jesse Welch", "Agency Legal Counsel", "Seattle", 28, 200000],
["Eli Mejia", "Commercial Specialist", "Long Beach", 65, 400000],
["Gene Leblanc", "Industrial Analyst", "Hartford", 34, 110000],
["Danny Leon", "Computer Scientist", "Newark", 60, 220000],
["Lane Lee", "Corporate Counselor", "Cincinnati", 52, 180000],
["Jesse Hall", "Business Analyst", "Baltimore", 44, 99000],
["Danni Hudson", "Agency Legal Counsel", "Tampa", 37, 90000],
["Terry Macdonald", "Commercial Specialist", "Miami", 39, 140000],
["Justice Mccarthy", "Attorney", "Tucson", 26, 330000],
["Silver Carey", "Computer Scientist", "Memphis", 47, 250000],
["Franky Miles", "Industrial Analyst", "Buffalo", 49, 190000],
["Glen Nixon", "Corporate Counselor", "Arlington", 44, 80000],
[
"Gabby Strickland",
"Business Process Consultant",
"Scottsdale",
26,
45000
],
["Mason Ray", "Computer Scientist", "San Francisco", 39, 142000]
];
const options = {
filter: true,
filterType: "dropdown",
responsive: "stacked"
};
return (
<MuiThemeProvider theme={this.getMuiTheme()}>
<MUIDataTable
title={"ACME Employee list"}
data={data}
columns={columns}
options={options}
/>
</MuiThemeProvider>
);
}
}
window.setInterval(() => {
window.table.forceUpdate();
},5000)
ReactDOM.render(<App />, document.getElementById("root"));
Tested here:
https://codesandbox.io/embed/0ylq1lqwp0?autoresize=1&hidenavigation=1
Steps to reproduce:
Select a filter. Wait 5 seconds. -> Everything will be reset
Expected behavior:
All table state information are kept after re-render.
Note:
This doesn't re-produce the exact issue. With the version I'm testing what is interesting is that the rows per page are not reset, but the filters are reset, maybe this helps.
After further digging into the issue getting closer to the core issue:
Adding:
onTableChange: (action, tableState) => {
this.props.onTableChange(tableState);
},
searchText: this.props.outerState().searchText,
while saving the state outside the component, and passing via outerState()
will lead to searchText persisting/not getting reset during a re-render.
Now the only thing I'm seeing missing is the filters.
Passing filterList/filterData/columns from outerState into options does not achieve the desired result (leaves filters fully unaffected).
How can the filters be persistingly passed into the table?
Also having issues. How can I set the tablestate?
Would like to do this:
const handleTableInit = (action, tableState) => {
console.log('handleTableInit: ', tableState)
if (_.isEqual({}, tableMultiSelect)) {
setTableMultiSelect(tableState)
} else {
tableState = tableMultiSelect
}
}
I have a stepper with different tables and I want to keep the filtering if the user go back. I just need to set the table to the previous value if it is empty.
In the source code of MUIDataTable there is interestingly an implementation of the prevState
if (this.options.serverSide) {
newState = {
...newState,
data: prevState.data,
displayData: prevState.displayData,
selectedRows: prevState.selectedRows,
};
}
It'd be great to have an option like "persisState" to keep the state after re-rendering.
I believe this is by design. The project is moving towards having the table state managed outside of the table, that way props are reflected in the table whenever the table is re-rendered (see https://github.com/gregnb/mui-datatables/issues/679). As for saving the filters, here's one possible way:
https://codesandbox.io/s/muidatatables-custom-toolbar-cxrdn
Not sure if this is the best way, I don't use filters in my project, but this is one way that works. Basically whenever a filter is changed, it's info is saved onto a prop that we pass into MuiDatatables. That way whenever we re-render the table with the props, the props have the most up-to-date information.
This code similuates the re-rendering:
window.setInterval(() => {
setVal(val + 1);
}, 10000);
Simply comment out the code inside of "onFilterChange" to see the filters erased every 10 seconds.
@patorjk : Thanks. Now the only problem with this is if the columns are passed as props. That will automatically reset to the props on render.
But: This works if the columns are not updated via componentWillReceiveProps. This fix seems sufficent for me, although the snippet seems to cause a RAM leak in my browser. But could be related to codesandbox aswell, haven't looked into this further.
Seems like an acceptable fix to me.
The same fix suggested by @patorjk works for the columns (code slightly adjusted to class):
onColumnViewChange: (changedColumn, display) => {
console.log(name, display);
var newCols = this.columns.slice();
for (var ii = 0; ii < newCols.length; ii++) {
if (newCols[ii].name === changedColumn) {
newCols[ii].options.display = display == 'add'?true:false;
}
}
this.setColumns(newCols);
},
Thank you @Fabbok1x and @patorjk for helping me to reproduce.
This is a known issue, and it is partly related to some tension in the design of the table (internal derived state vs. props, as @patorjk alluded to).
The workaround for now is to manage your own state whenever you notice an issue like this. In the case of the filters, you can manage it via filterList in the column options (so whenever a filter would change, either through user action or table refresh, you add to the appropriate column manually). There is also some advice in this issue about column refreshing, which is a little different of an issue, but I offer a codesandbox solution in case it would be useful to you to see how I worked around that one: https://github.com/gregnb/mui-datatables/issues/784.
I recently had to overcome this obstacle as well. Thanks everyone for your tips. Here is what I came up with for externally managing state. Everything is done in a parent component which renders the table:
1) Set up an object in state:
constructor(props) {
super(props);
this.state = {
tableStatePersist: { //Dynamic collection of props that are needed between table refreshes.
searchText: '',
filterList: [],
columns: []
},
};
}
tableStatePersiststores the table settings that we want to keep, and will be processed by our new functiongetColumns()(below) before the data is passed into theMUIDataTable'scolumnsprop.
2) Create a function to manage the state object tableStatePersist:
handleChange = (action, tableState) => {
console.log("Table state changed || " + JSON.stringify(action));
//console.log("CURRENT STATE: " + JSON.stringify(tableState));
if(action !== 'propsUpdate') { //Store new tableStatePersist only if not updating props
this.setState({
tableStatePersist: {
searchText: tableState.searchText,
filterList: tableState.filterList, //An array of filters for all columns
columns: tableState.columns //We can pull column-specific options from this array, like display and sortDirection
},
});
}
};
handleChange()is to be assigned to theMUIDataTable's propoptions.onTableChange. We use and test theactionargument passed byonTableChangeto be smarter about when to update.onTableChange'stableStateargument is essential to making this work.
3) Create a function to return the columns along with all settings changes that need to persist across re-renders:
//Return all columns, their props, and any current state-related changes
getColumns = () => {
//Define all of the alert table's columns and their default props and options as per the mui-datatables documentation
let columns = [
{...},
];
//Loop thru columns and assign all column-specific settings that need to persist thru a data refresh
for(let i = 0; i < columns.length; i++) {
//Assign the filter list to persist
columns[i].options.filterList = this.state.tableStatePersist.filterList[i];
if(this.state.tableStatePersist.columns[i] !== undefined) {
//If 'display' has a value in tableStatePersist, assign that, or else leave it alone
if(this.state.tableStatePersist.columns[i].hasOwnProperty('display'))
columns[i].options.display = this.state.tableStatePersist.columns[i].display;
//If 'sortDirection' has a value in tableStatePersist, assign that, or else leave it alone
if(this.state.tableStatePersist.columns[i].hasOwnProperty('sortDirection')) {
//The sortDirection prop only permits sortDirection for one column at a time
if(this.state.tableStatePersist.columns[i].sortDirection != 'none')
columns[i].options.sortDirection = this.state.tableStatePersist.columns[i].sortDirection;
}
}
}
return columns;
}
getColumns()will be used to return the columns array directly toMUIDataTable'scolumnsprop.
If you followed each step and the extra instructions within each step, you should now have a table that is carrying over the states that you need! Note that, to carry over searchText I defined a function to fetch it from state:
getSearchText = () => {
return this.state.tableStatePersist.searchText;
}
Then assigned said function to the MUIDataTable component's options.searchText prop.
Your datatable's render declaration should include the following/look similar to:
<MUIDataTable
title='Words'
options={{
(...),
searchText: this.getSearchText(),
onTableChange: (action, tableState) => this.handleChange(action, tableState)
}}
columns={this.getColumns()}
data={data}
/>
Where do you pass this.state.tableStatePersist to MUIDataTable? Does selecting rows work for you?
@kkaminski-github See step 3. this.state.tablePersist is not passed to MUIDataTable , but is used by getColumns() and getSearchText() . getColumns() is the function that returns all column info with persistent options. You must then assign that value to this.state.datatableColumns , which is, as shown in the example, assigned to the datatable's columns prop.
You want to assign getColumns() from within componentDidMount() and also right before any other line of code that will cause a table refresh, such as repopulating the rows data from a server or something.
As for selecting rows, I haven't tried it but I'm confident you can expand on my example implementation to carry over row selection state, too. A good starting point is to examine the object layout of tableState (just uncomment the debug console.log() line that outputs tableState JSON.stringify() 'd -- this is in handleChange() ). Depending on where row selection state is, you can pull from there to pass to the rowsSelected prop of MUIDataTable.
(Don't take my word for it though, I don't have the documentation here so I'm not sure where the option is.)
@SaberMage Is there a reason you can't store your state normally and pass it into the table, as in the example column-options-update?
@SaberMage Is there a reason you can't store your state normally and pass it into the table, as in the example
column-options-update?
@gabrielliwerant I kind of see what you are suggesting. Really, my approach achieves a similar outcome to the example you mentioned and by similar means. The key differences are:
display and filterList. In mine, these are defined inline along with all the other column options. In that example, they are defined in state which I acknowledge is likely a _technical_ better practice, but is ultimately a little more confusing/cumbersome to read.column-options-update, the only state auto-saved is the filterLists. The method I shared auto-saves all desired states anytime changes are made, so they may carry across re-renders. onTableChange is a useful event for knocking them all out in one spot.Looking back at it, my method can probably be streamlined a bit more. For instance, I imagine getColumns() could be assigned directly to the columns prop of the MUIDataTable, meaning the state variable datatableColumns could be dropped altogether. (EDIT: Went ahead and revised my post to reflect this. state.datatableColumns is no more.)
@SaberMage Sure, just thought I'd bring it up in case it would be beneficial to anyone. One of the goals of the table is to allow users to seed it initial data and then control it afterwards via state, although it is certainly not as easy or clear to do as it could be.
I gave up storing the tableState in the component state. And I'm going to leave this as a RFC or an idea for anyone facing the same problem.
The basic approach is, every time the onTableChange is triggered, the table state will be stored using this.setState.
Now, imagine you are searching something, you type in a lot of text. Every character will trigger a table state change. Which means the table will re-render (with the new searchText as initial state) as many times as the amount of characters. In my case, state updates would get grouped, and typing would become really slow.
My alternative was to store that outside of the state, avoiding those extra re-renders. If by any means, the re-render method is called, only then I will retrieve the searchText again.
@pedroo21 A good way to tackle this sort of problem is to handle the search text action of onTableChange differently from other onTableChange actions. More specifically, I recommend using setTimeout to update the state.
Here is a brainstorm of the steps:
That's all. Now, the event of storing state is queued out by 1 second and that timer is canceled and reset every time the user changes the search text. Since people generally type more than one letter per second, this will result in a greatly reduced frequency of re-renders (the user must stop changing the text for at least a full second) caused by storing the updated table state. Of course, you can change the timing to whatever seems to work best, like maybe 3 sec would make more sense in some scenarios.
@pedroo21 you could use the customSearchRender method to create a search that only calls the table's internal search when the user has stopped typing.
As an aside, I've been experiementing with changing how the table works under the covers. One idea I had was to rollover state values for fields that the developer hasn't provided in the optionsobject. So things like searchText, filters, sorting, etc, wouldn't reset when the table re-renders. You'd only have to track what you're controlling.
It was actually pretty simple to do. For example, below is how to make searchText persistent (this would go in the setTableData function):
let searchText = status === TABLE_LOAD.INITIAL ? this.options.searchText : null;
if (typeof this.options.searchText === 'undefined' && typeof this.state.searchText !== 'undefined') {
searchText = this.state.searchText;
}
So far the only issues I've seen is that rowsExpanded and rowsSelected need to be reset if the data changes. But persisting other data doesn't (so far) seem to have any negative effects.
This PR would fix the issue: https://github.com/gregnb/mui-datatables/pull/1086
Hi! One question... I'm having some problems trying to store the column state after sorting and re-rendering the table. I'm using redux so I'm saving the "new state" on the store but when trying to set the table state on onTableInit, it's not working...
onTableInit fires after the table initializes, which includes the data, so it is not the right place to set state, as it will be too late. You do not need hooks into the table to use redux anyway, you just use as normal with your state that you feed into the table.
onTableInitfires after the table initializes, which includes the data, so it is not the right place to set state, as it will be too late. You do not need hooks into the table to use redux anyway, you just use as normal with your state that you feed into the table.
Ok, but the problem is when I change from that view to another and then go back to that previews view, the all the filters I made are lost
@SaberMage thank you for that work-around. I spent three days trying to fix this issue and your solution worked!
My issue was that I had a state variable that was being changed in the same component that I was rendering my MUI DataTable. Example:
const ExampleComponent = () => {
const [foo, setFoo] = useState();
return <MUIDataTable />
}
When I used setFoo(), the sorting was reset so what I did to persist this was by saving the direction and changedColumn to the component's local state and then set the sort direction on each individual column such as options: {
sortDirection: sortBy === 'column_name' ? direction : 'none',
},
You can use the onColumnSortChange option that will give you those two variables and you can save them to your local state. I hope this helped!
The strange thing is that the page and rows per page don't get reset when I use setFoo() and the parent component is re-rendered.
@patorjk 's PR seems very promising! It would fix this issue. I hope it gets merged :pray:
Hello, just wanted to post, what I've come up with. Maybe, it may help somebody:
2. Then I created a function to append the saved state to any columns:
function apendFilterListToColumns(
columns: {
name: string;
label: string;
options: MUIDataTableColumnOptions
}[],
filterList: string[][]
) {
filterList.forEach((filter, index) => {
columns[index].options.filterList = filter;
});
return columns;
}
3. Then in the table I used this function and in onTableChange I am listening for changes of search and filter:
data={data}
columns={apendFilterListToColumns(
columns,
filterAndSearchPersist.filterList
)}
options={{
searchText: filterAndSearchPersist.searchText ?? undefined,
onTableChange: (action, tableState) => {
switch (action) {
case "filterChange":
setFilterAndSearchPersist({
...filterAndSearchPersist,
filterList: tableState.filterList,
});
break;
case "search":
setFilterAndSearchPersist({
...filterAndSearchPersist,
searchText: tableState.searchText,
});
}
},
```
I hope this helps somebody.
Table state is now persistent in the table as of version 2.15.0
Table state is now persistent in the table as of version 2.15.0
What do you mean 2.15.0? Are you still releasing 2x versions?
Table state is now persistent in the table as of version 2.15.0
What do you mean 2.15.0? Are you still releasing 2x versions?
No, this is an old issue. I closed it and just mentioned the version when it would have been fixed (2.15.0 was the first version I did as a maintainer and consisted of one of the PRs I mentioned above). I came upon this while researching another issue and closed it when I realized what it was referring to.
When I get some time I'll probably go through and close more issues, but closing issues is a low priority so it probably won't happen any time soon, but when I see an issue that can be closed, I'll close it.
Most helpful comment
I recently had to overcome this obstacle as well. Thanks everyone for your tips. Here is what I came up with for externally managing state. Everything is done in a parent component which renders the table:
1) Set up an object in state:
2) Create a function to manage the state object
tableStatePersist:3) Create a function to return the columns along with all settings changes that need to persist across re-renders:
If you followed each step and the extra instructions within each step, you should now have a table that is carrying over the states that you need! Note that, to carry over
searchTextI defined a function to fetch it from state:Then assigned said function to the
MUIDataTablecomponent'soptions.searchTextprop.Your datatable's render declaration should include the following/look similar to: