Search code examples
reactjsmaterial-uimui-datatable

How to prevent re-rendering while typing


I have a React page that looks like this:
enter image description here

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 >
    )
}

Solution

  • 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