I have a React page that looks like this:
The problem is that by every keystroke in one of the textfields on the left it causes the table on the right to re-render which makes typing very slow.
I tried to use "useMemo" in the Datatable component hoping that it prevents the table to re-render but it doesn't help.
I probably do something wrong but I don't know what and now I'm really stocked.
Any help would be much appreciated.
Here some code:
Datatable.js
import MUIDataTable from 'mui-datatables';
import { createTheme, ThemeProvider } from '@mui/material/styles';
const Datatable = React.memo((props)=>
{
const getMuiTheme = () =>
createTheme({
//...here some styling
return (
<ThemeProvider theme={getMuiTheme()}>
< MUIDataTable
sx={{
marginLeft: '300px',
}}
title={props.title}
data={props.data}
columns={props.columns}
options={props.options}
/>
</ThemeProvider>
)
})
export default Datatable
Customers.js
import AlertMessage from '../../components/alert/AlertMessage';
import Datatable from '../../components/others/Datatable';
import { Box, Drawer, CssBaseline, TableRow, TableCell, Switch, ButtonGroup, IconButton, Fab, FormControlLabel, MenuItem } from '@mui/material';
import MuiTextfield from '../../components/fields/MuiTextfield';
import MuiTextfieldSelect from '../../components/fields/MuiTextfieldSelect';
import KeyIcon from '@mui/icons-material/Key';
import EditIcon from '@mui/icons-material/Edit';
import CheckIcon from '@mui/icons-material/Check';
import CancelIcon from '@mui/icons-material/Cancel';
import APIServiceCustomers from '../../APIServices/APIServiceCustomers'
import APIServiceAgents from '../../APIServices/APIServiceAgents'
import APIServiceUsers from '../../APIServices/APIServiceUsers'
import APIServiceStations from '../../APIServices/APIServiceStations'
import moment from 'moment';
const SPACED_DATE_FORMAT = 'DD MMM YYYY';
export default function Customers()
{
const [data, setData] = useState([])
const [users, setUsers] = useState([])
const [pst, setPst] = useState([])
const [id, setId] = useState('');
const [company, setCompany] = useState('');
const [address, setAddress] = useState('');
const [zip, setZip] = useState('');
const [city, setCity] = useState('');
const [country, setCountry] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [selectedAgent, setSelectedAgent] = useState('');
const [agents, setAgents] = useState([]);
const [active, setActive] = useState(false)
const [editing, setEditing] = useState(false);
const [readOnly, setReadOnly] = useState(false)
//alert
const [open, setOpen] = useState(false);
const [severity, setSeverity] = useState('success');
const [message, setMessage] = useState('');
//styles
const cellStyle = {
paddingLeft: "40px",
background: 'rgb(113, 113, 129)',
whiteSpace: 'nowrap',
// width: 'auto',
borderBottom: 'none'
}
const titleCellStyle = {
paddingLeft: "40px",
// background: 'rgb(113, 113, 129)',
whiteSpace: 'nowrap',
// width: 'auto',
borderBottom: 'none'
}
//columns
const columns = useMemo(() =>
[
{
label: 'Id',
name: 'cust_id',
options: {
filter: false,
sort: false,
}
},
{
label: 'Name',
name: 'cust_name',
options: {
sort: true,
}
},
{
label: 'Address',
name: 'cust_address',
options: {
filter: true,
sort: true,
display: false
}
},
{
label: 'Zip',
name: 'cust_zip',
options: {
filter: true,
sort: true,
display: false
}
},
{
label: 'City',
name: 'cust_city',
options: {
filter: true,
sort: true,
display: false
}
},
{
label: 'Country',
name: 'cust_country',
options: {
filter: true,
sort: true,
display: false
}
},
{
label: 'Email',
name: 'cust_email',
options: {
filter: true,
sort: true,
}
},
{
label: 'Phone',
name: 'cust_phone',
options: {
filter: true,
sort: true,
}
},
{
label: 'Agent',
name: 'agents.age_name',
options: {
filter: true,
sort: true,
}
},
{
label: 'Active',
name: 'cust_active',
options: {
filter: true,
sort: true,
customBodyRender: (value, tableMeta, updateValue) =>
{
return (
<div>
<Switch checked={value} />
</div>
);
}
}
},
{
label: 'Ceration Date',
name: 'cust_created',
options: {
filter: true,
sort: true,
customBodyRender: value =>
moment(new Date(value)).format(SPACED_DATE_FORMAT)
}
},
{
//edit button
label: 'Actions',
name: '',
empty: true,
options: {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) =>
{
return (
<ButtonGroup>
<IconButton aria-label='edit'
onClick={() =>
{
let rowData = tableMeta.rowData
editRow(rowData)
}} component='span'>
<EditIcon />
</IconButton>
<IconButton aria-label='generate token'
onClick={() =>
{
let rowData = tableMeta.rowData
generateToken(rowData)
}} component='span'>
<KeyIcon />
</IconButton>
</ButtonGroup >
);
}
}
},
]
)
//column options
const options = {
tableBodyMaxHeight: 'none',
filter: true,
sort: true,
print: false,
download: true,
downloadOptions: {
filterOptions:
{
useDisplayedColumnsOnly: true,
useDisplayedRowsOnly: true
}
},
draggableColumns: {
enabled: true
},
pagination: false,
search: true,
responsive: "vertical",
selectableRowsHeader: true,
sortFilterList: true,
viewColumns: true,
selectableRows: 'multiple',
selectToolbarPlacement: 'above',
expandableRows: true,
renderExpandableRow: (rowData, rowMeta) =>
{
const colSpan = rowData.length - 1;
return (
<>
{/* Address */}
<TableRow >
<TableCell sx={titleCellStyle} colSpan={colSpan}>Address</TableCell>
</TableRow>
<TableRow >
<TableCell colSpan={colSpan} sx={cellStyle}> {rowData[2] ' -\t ' rowData[3] ' - ' rowData[4] ' - ' rowData[5]}</TableCell>
</TableRow>
{/* Users */}
<TableRow >
<TableCell sx={titleCellStyle} colSpan={colSpan}>Users</TableCell>
</TableRow>
{users.filter((u) => u.usr_cust_id === rowData[0]).length > 0 ?
users.filter((u) => u.usr_cust_id === rowData[0]).map((usersRow) => (
<TableRow key={usersRow.usr_created}>
<TableCell sx={cellStyle} >{usersRow.usr_name}</TableCell>
<TableCell sx={cellStyle} >{usersRow.usr_email}</TableCell>
<TableCell sx={cellStyle} colSpan={colSpan} >{moment(usersRow.usr_created).format(SPACED_DATE_FORMAT)}</TableCell>
</TableRow>
))
:
<TableRow >
<TableCell sx={cellStyle} colSpan={colSpan} >none</TableCell>
</TableRow>
}
{/* Stations */}
<TableRow >
<TableCell sx={titleCellStyle} colSpan={colSpan}>Stations</TableCell>
</TableRow>
{pst.filter((p) => p.pst_cust_id === rowData[0]).length > 0 ?
pst.filter((p) => p.pst_cust_id === rowData[0]).map((pstRow) => (
<TableRow key={pstRow.pst_created}>
<TableCell sx={cellStyle}>{pstRow.pst_name}</TableCell>
<TableCell sx={cellStyle}>{pstRow.pst_address}</TableCell>
<TableCell sx={cellStyle}>{pstRow.pst_zip}</TableCell>
<TableCell sx={cellStyle} colSpan={colSpan} >{pstRow.pst_city}</TableCell>
</TableRow>
)) :
<TableRow >
<TableCell sx={cellStyle} colSpan={colSpan} >none</TableCell>
</TableRow>
}
</>
);
},
};
//data fetching
const fetchData = async () =>
{
await APIServiceCustomers.getCustomers()
.then(data => { setData(data) })
.catch(console.error);
await APIServiceAgents.getAgents()
.then(agents => { setAgents(agents) })
.catch(console.error);
await APIServiceUsers.getUsers()
.then(users => { setUsers(users) })
.catch(console.error);
await APIServiceStations.getStations()
.then(pst => { setPst(pst) })
.catch(console.error);
}
useEffect(() =>
{
fetchData()
}, [])
//----------------------------here all kind of functions--------------------------
...
//--------------------------------------------------view---------------------------
return (
<Box container sx={{ background: 'transparent', position: 'fixed', display: 'flex', marginTop: '187px', width: '100%', height: '80%', }}>
<AlertMessage
open={open}
severity={severity}
message={message}
close={handleClose}
/>
<CssBaseline />
<Drawer sx={{
background: 'transparent',
flexShrink: 0,
width: '250px',
'& .MuiDrawer-paper': {
top: '187px',
width: '250px',
background: 'transparent',
borderRight: 1,
borderColor: "#6C90A9",
},
}}
variant="permanent"
anchor="left"
>
<input
type='hidden'
value={id}
name='id'
/>
<MuiTextfield
id='company'
label='Company'
value={company}
size='small'
onChange={useMemo(
() => (e) => setCompany(e.target.value), [])}
/>
<MuiTextfield
id='address'
label='Address'
value={address}
size='small'
onChange={useMemo(
() => (e) => setAddress(e.target.value), [])}
tabIndex="1"
/>
<MuiTextfield
id='zip'
label='Zip'
value={zip}
size='small'
onChange={useMemo(
() => (e) => setZip(e.target.value), [])}
tabIndex="2"
/>
<MuiTextfield
id='city'
label='City'
value={city}
size='small'
onChange={useMemo(
() => (e) => setCity(e.target.value), [])}
/>
<MuiTextfield
id='country'
label='Country'
value={country}
size='small'
onChange={useMemo(
() => (e) => setCountry(e.target.value), [])}
/>
<MuiTextfield
id='email'
label='Email'
value={email}
size='small'
onChange={useMemo(
() => (e) => setEmail(e.target.value), [])}
/>
<MuiTextfield
id='phone'
label='Phone'
value={phone}
size='small'
onChange={useMemo(
() => (e) => setPhone(e.target.value), [])}
/>
<MuiTextfieldSelect
id='agent'
label='Agent'
size='small'
disabled={readOnly}
value={selectedAgent}
onChange={handleChangeAgent}
options={agents.map((age, index) => (
<MenuItem sx={{ backgroundColor: 'rgb(40,40,68)' }} key={index} value={age.age_id}>
{age.age_name}
</MenuItem>
))}
/>
<FormControlLabel
sx={{ color: 'white', marginTop: '20px' }}
control={<Switch
value={active}
id='active'
checked={active}
size='small'
onChange={(event) => setActive(event.target.checked)}
/>}
label='Active'
labelPlacement='top'
/>
<Box
sx={{
display: 'flex',
justifyContent: "center",
width: '80%',
padding: "20px",
marginLeft: '20px'
}} >
< Fab aria-label='ok'
color='secondary' sx={{ mr: 2 }} size='small' onClick={curd}>
<CheckIcon />
</Fab>
< Fab aria-label='cancel'
color='primary' size='small' sx={{ ml: 2 }} onClick={reset}>
<CancelIcon />
</Fab>
</Box>
</Drawer>
{/* rightSide */}
<div style={{ overflowY: 'auto', height: "80vh", width: '100vw', }}>
<Datatable
title={'Customers'}
data={data}
columns={columns}
options={options}
/>
</div>
</Box >
)
}
CodePudding user response:
I would recommend you to separate the components for example:
InputFields.js
DataTable.js
index.js
Place all the state and input fields inside InputFields.js
DataTable inside DataTable.js
and
fetch function
inside index.js
then pass the data to DataTable.js
as props. This will help you with input lag also it will be more readable. Another work-around is to use onBlur
instead of onChange
, This will update the state when user unfocused the input field, just replace them, the rest of the code is the same, but I recommend you to use the first variant
CodePudding user response:
I suppose you have a lot of issues there. One I can see is that
const columns = useMemo
has no dependencies which means it is calculated on each render (you should use an empty array as a dependency I suppose). This is a prop for your table so the table will be re-rendered on each keystroke as it sets the state of the parent component - so the columns are changing so the props for the table so it has to be rendered. Options also are const inside the function so it is another const each time the component sets the state.