Home > Software design >  How to prevent re-rendering while typing
How to prevent re-rendering while typing

Time:09-27

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

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.

  • Related