How to prevent mui datatables render when set states in onRowSelectionChange - javascript

I'm currently working on React js project with "mui-datatables": "^4.2.2".
I have a list of data divided on pages with the possibility of selecting an item through a checkbox :
My problem :
when I select an item in the second page, the component rerender and automatically back to the first page.
I think the problem is a set state inside onRowSelectionChange :
const options = {
filter: false,
onRowClick: (rowData, rowMeta) => {
console.log("--rowData --");
console.log(rowData);
console.log("--rowMeta --");
console.log(rowMeta);
},
onRowSelectionChange: (
currentRowsSelected,
allRowsSelected,
rowsSelected
) => {
let items = [];
allRowsSelected.forEach((row) => {
items.push(data[row.dataIndex]);
});
setSelectedRows(items);
},
How can i fix this problem ?

you should store page number in the state as well say for example
curPage:1
when page change you should update the curPage as well,now you can use this inside the options props you pass to mui-datatable.like this
const options = {
page:this.state.curPage,//incase its class component
onChangePage:(page)=>{
this.setState({curPage:page});//assuming its a class component
},
filter: false,
onRowClick: (rowData, rowMeta) => {
console.log("--rowData --");
console.log(rowData);
console.log("--rowMeta --");
console.log(rowMeta);
},
onRowSelectionChange: (
currentRowsSelected,
allRowsSelected,
rowsSelected
) => {
let items = [];
allRowsSelected.forEach((row) => {
items.push(data[row.dataIndex]);
});
setSelectedRows(items);
},
hope this will help

Related

State changing but not rendering

I want to render a settings menu onclick when an element is added to my list and disable all other setting menus when one is active. I also have a sample element in a different state and would like to deactivate all setting menus when it is on and have it disabled if a settings menu is on. I am storing the setting states of all elements in an array and in my update function the copy of my setting state is changing but my actual settings state isn't. I think it has to do with my useeffect and when I comment it out my settings state does change but it isn't rendered. It was working before I mapped my chart data, but I am not sure what caused it to stop working as commenting it out still does not render settings.
const [settings, setSettings] = useState([]);
const [sampSettings, setSampSettings] = useState(false);
const updateSettings = (val, index) => {
if(val){
let copySettingsFalse = [...settings]
copySettingsFalse[index]=false;
return setSettings(copySettingsFalse);
}
let copySettingsTrue = settings.fill(false);
copySettingsTrue[index] = true;
console.log(copySettingsTrue)
return setSettings(copySettingsTrue);
};
useEffect(() => {
if (stockList.length>0){
stockList.map((val,index) => {
// Chart
setLineData([
...lineData,
{
labels:trimLineDataHistory[index],
datasets: [
{
label: val.Tick,
data: trimLineDataHistory[index].reverse(),
pointRadius: 0,
fill: false,
backgroundColor: day ? "#e9dfd4" : "#141e28",
borderColor: "#52ad59",
},
],
},
]);
// Settings
setSettings([...settings, false]);
})}
return;
},[stockList]);
// Settings
// useEffect(() => {
// if (settings.includes(true)) {
// setSampSettings(false);
// }
// if (sampSettings === true) {
// setSettings(settings.fill(false));
// }
// return;
// }, [settings,sampSettings]);
if(stockList.length>1)
return(
<>
{stockList.map((val, index) => {
const { Tick, Name } = val;
return(
<div className="stockItemIcon">
<BsFillGearFill
className="gearIcon"
onClick={() => updateSettings(settings[index], index)}></BsFillGearFill>
<div className={settings[index]?
(day?"settingsContainerAlt showSettings daySettings":"settingsContainerAlt showSettings nightSettings")
:(day?"settingsContainerAlt hideSettings daySettings":"settingsContainerAlt hideSettings nightSettings")}>
</div>)}
</>)
You try to add the "Key" attribute BsFillGearFill tag and also set & update value for it. it may be any value

React child component not re-rendering on updated parent state

I've tried to find a solution to this, but nothing seems to be working. What I'm trying to do is create a TreeView with a checkbox. When you select an item in the checkbox it appends a list, when you uncheck it, remove it from the list. This all works, but the problem I have when I collapse and expand a TreeItem, I lose the checked state. I tried solving this by checking my selected list but whenever the useEffect function runs, the child component doesn't have the correct parent state list.
I have the following parent component. This is for a form similar to this (https://www.youtube.com/watch?v=HuJDKp-9HHc)
export const Parent = () => {
const [data,setData] = useState({
name: "",
dataList : [],
// some other states
})
const handleListChange = (newObj) => {
//newObj : { field1 :"somestring",field2:"someotherString" }
setDataList(data => ({
...data,
dataList: data.actionData.concat(newObj)
}));
return (
{steps.current === 0 && <FirstPage //setting props}
....
{step.current == 3 && <TreeForm dataList={data.dataList} updateList={handleListChange}/>
)
}
The Tree component is a Material UI TreeView but customized to include a checkbox
Each Node is dynamically loaded from an API call due to the size of the data that is being passed back and forth. (The roots are loaded, then depending on which node you select, the child nodes are loaded at that time) .
My Tree class is
export default function Tree(props) {
useEffect(() => {
// call backend server to get roots
setRoots(resp)
})
return (
<TreeView >
Object.keys(root).map(key => (
<CustomTreeNode key={root.key} dataList={props.dataList} updateList={props.updateList}
)))}
</TreeView>
)
CustomTreeNode is defined as
export const CustomTreeNode = (props) => {
const [checked,setChecked] = useState(false)
const [childNodes,setChildNodes] = useState([])
async function handleExpand() {
//get children of current node from backend server
childList = []
for( var item in resp) {
childList.push(<CustomTreeNode dataList={props.dataList} updateList={props.updateList} />)
}
setChildNodes(childList)
}
const handleCheckboxClick () => {
if(!checked){
props.updateList(obj)
}
else{
//remove from list
}
setChecked(!checked)
}
// THIS IS THE ISSUE, props.dataList is NOT the updated list. This will work fine
// if I go to the next page/previous page and return here, because then it has the correct dataList.
useEffect(() => {
console.log("Tree Node Updating")
var isInList = props.dataList.find(function (el) {
return el.field === label
}) !== undefined;
if (isInList) {
setChecked(true);
} else {
setChecked(false)
}
}, [props.dataList])
return ( <TreeItem > {label} </TreeItem> )
}
You put props.data in the useEffect dependency array and not props.dataList so it does not update when props.dataList changes.
Edit: Your checked state is a state variable of the CustomTreeNode class. When a Tree is destroyed, that state variable is destroyed. You need to store your checked state in a higher component that is not destroyed, perhaps as a list of checked booleans.

react-widgets DropDownList dynamic load on demand

I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this 😇
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.

setState long execution when managing large amount of records

I am trying to solve a problem that happens in react app. In one of the views (components) i have a management tools that operate on big data. Basically when view loads i have componentDidMount that triggers ajax fetch that downloads array populated by around 50.000 records. Each array row is an object that has 8-10 key-value pairs.
import React, { Component } from "react";
import { List } from "react-virtualized";
import Select from "react-select";
class Market extends Component {
state = {
sports: [], // ~ 100 items
settlements: [], // ~ 50k items
selected: {
sport: null,
settlement: null
}
};
componentDidMount() {
this.getSports();
this.getSettlements();
}
getSports = async () => {
let response = await Ajax.get(API.sports);
if (response === undefined) {
return false;
}
this.setState({ sports: response.data });
};
getSettlements = async () => {
let response = await Ajax.get(API.settlements);
if (response === undefined) {
return false;
}
this.setState({ settlements: response.data });
};
save = (key, option) => {
let selected = { ...this.state.selected };
selected[key] = option;
this.setState({ selected });
};
virtualizedMenu = props => {
const rows = props.children;
const rowRenderer = ({ key, index, isScrolling, isVisible, style }) => (
<div key={key} style={style}>
{rows[index]}
</div>
);
return (
<List
style={{ width: "100%" }}
width={300}
height={300}
rowHeight={30}
rowCount={rows.length || 1}
rowRenderer={rowRenderer}
/>
);
};
render() {
const MenuList = this.virtualizedMenu;
return (
<div>
<Select
value={this.state.selected.sport}
options={this.state.sports.map(option => {
return {
value: option.id,
label: option.name
};
})}
onChange={option => this.save("sport", option)}
/>
<Select
components={{ MenuList }}
value={this.state.selected.settlement}
options={this.state.settlements.map(option => {
return {
value: option.id,
label: option.name
};
})}
onChange={option => this.save("settlement", option)}
/>
</div>
);
}
}
The problem i am experiencing is that after that big data is downloaded and saved to view state, even if i want to update value using select that has ~100 records it takes few seconds to do so. For example imagine that smallData is array of 100 items just { id: n, name: 'xyz' } and selectedFromSmallData is just single item from data array, selected with html select.
making a selection before big data loads takes few ms, but after data is loaded and saved to state it suddenly takes 2-4 seconds.
What would possibly help to solve that problem (unfortunately i cannot paginate that data, its not anything i have access to).
.map() creates a new array on every render. To avoid that you have three options:
store state.sports and state.settlements already prepared for Select
every time you change state.sports or state.settlements also change state.sportsOptions or state.settlementsOptions
use componentDidUpdate to update state.*Options:
The third option might be easier to implement. But it will trigger an additional rerender:
componentDidUpdate(prevProps, prevState) {
if (prevState.sports !== this.state.sports) {
this.setState(oldState => ({sportsOptions: oldState.sports.map(...)}));
}
...
}
Your onChange handlers are recreated every render and may trigger unnecessary rerendering of Select. Create two separate methods to avoid that:
saveSports = option => this.save("sport", option)
...
render() {
...
<Select onChange={this.saveSports}/>
...
}
You have similar problem with components={{ MenuList }}. Move this to the state or to the constructor so {MenuList} object is created only once. You should end up with something like this:
<Select
components={this.MenuList}
value={this.state.selected.settlement}
options={this.state.settlementsOptions}
onChange={this.saveSettlements}
/>
If this doesn't help consider using the default select and use a PureComponent to render its options. Or try to use custom PureComponents to render parts of the Select.
Also check React-select is slow when you have more than 1000 items
The size of the array shouldn't be a problem, because only the reference is stored in the state object, and react doesn't do any deep equality on state.
Maybe your render or componentDidUpdate iterates over this big array and that causes the problem.
Try to profile your app if this doesn't help.

Persisting state across tables on event change

I have a table of schedules that is rendered by a dropdown. Each schedule can then be marked for export via a slider, this will store the schedule id in scheduleIdsToExport and show that schedule in the export table.
But if I change the Select Query dropdown, which renders more schedules specific to that query, the schedules marked for export from the previous query disappear from the table. I want the schedules marked for export to persist in the table no matter what query is selected from the dropdown.
So I'm thinking I need to have something in my slider function to update state with the all the schedule objects marked for export and have them persist in the exported table. I'm not exactly sure how to go about storing all the schedules to keep them in the exported table and have the scheduleIdsToExport array also keep the id's of each schedule
slider = ({ id, isExported }) => {
if (isExported === true) {
this.setState(
{
scheduleIdsToExport: [id, ...this.state.scheduleIdsToExport]
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
} else {
const newArray = this.state.scheduleIdsToExport.filter(
storedId => storedId !== id
);
this.setState(
{
scheduleIdsToExport: newArray
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
}
};
The sandbox here will provide further explanation on what is happening.
This is just chaotic!
The problem : Keep track from multiples list of items(schedules) that will eventually be added to another list schedulesToExport
The Solution :
Create a parent component that reflects the previously described state
class Container extends Component{
state ={
querys :[
['foo','lore ipsum','it\'s never lupus'],
['bar','ipsumlorem', 'take the canolli']
],
selectedQuery : 0,
schedulesToExport : []
}
}
Now we have a list of lists, that can be interpreted as a list of querys containing a list of schedules
Render a select element to reflect each query:
render(){
const { querys, selectedQuery } = this.state
const options = querys.map((_, index) => (<option value={index}> Query: {index + 1}</option>))
return(
<div>
<select onChange={this.selectQuery}>
{options}
</select>
{
querys[selectedQuery].map(schedule => (
<button onClick={() => this.selectSchedule(index)}> Schedule: {schedule} </button>
))
}
</div>
)
}
What's happening? We are just rendering the selected query by it's index and showing all it's respective schedules.
Implement the selectQuery and selectSchedule methods which will add the selected schedule in the list to export:
selectQuery = e => this.setState({selectedQuery : e.target.value})
selectSchedule = index => {
const { selectedQuery } = this.state
const selected = this.state.querys[selectedQuery][index]
this.setState({
schedulesToExport: this.state.schedulesToExport.concat(selected)
})
}
That's it, now you a have a list of querys being displayed conditionally rendered selectedQuery props is just a index, but could be a property's name. You only see schedules from the current selected query, so when you click on schedule we just return it's index, and the state.querys[selectedQuery][index] will be your selected schedule, that is securely store in the state on a separated list.
I have updated your sandbox here.
In essence, it does not work in your example because of the following:
schedules
.filter(schedule =>
scheduleIdsToExport.includes(Number(schedule.id))
)
.map(schedule => {
return (
<Table.Row>
...
</Table.Row>
);
})
The value of schedules is always set to the current query, hence you end up showing schedules to export for the current query only.
A solution that changes very little of your code is to ditch scheduleIdsToExport altogether, and use schedulesToExport instead. Initially, we'll set schedulesToExport to an empty
object; we'll add schedules to it (keyed by schedule id) every time a schedule is selected - we'll delete schedules in the same way every time a schedule is unselected.
class App extends React.Component {
// ...
slider = ({ id, isExported }) => {
const obj = this.state.schedules.find(s => Number(s.id) === Number(id));
if (isExported === true) {
this.setState({
schedulesToExport: {
...this.state.schedulesToExport,
[id]: {
...obj
}
}
});
} else {
const newSchedulesToExport = this.state.schedulesToExport;
delete newSchedulesToExport[id];
this.setState({
schedulesToExport: newSchedulesToExport
});
}
};
// ...
}
You would then render the schedules to export as follows:
Object.keys(schedulesToExport).map(key => {
const schedule = schedulesToExport[key];
return (
<Table.Row>
...
</Table.Row>
);
})
Again, see more details in sandbox here.

Categories

Resources