Yuvraj Bohra
Yuvraj Bohra

Reputation: 3

Prevent refershing of Child Component in react hooks

I have created a common table component in which I am calling in the file that are needed under some table there is an field action which have an edit and delete icon whenever I click on edit icon it should trigger a dropdown that will allow to update the status of component. But when I click on edit icon the table component will refresh so the menu is not opening at suitable position. Please help me

TableComponent

import React, { useState } from 'react';
import { Table, TableHead, TableRow, TableCell, makeStyles, TablePagination, TableSortLabel } from '@material-ui/core';
import PropTypes from 'prop-types';

const useStyles = makeStyles(theme => ({
    table: {
        marginTop: theme.spacing(3),
        '& thead th': {
            fontWeight: '600',
            color:'white',
            backgroundColor: '#143174',
        },
        '& tbody td': {
            fontWeight: '300',
        },
        
    },
}))

export default function TableComponent(records, headCells,filterFn) {

    const classes = useStyles();

    const pages = [10, 50, 100]
    const [page, setPage] = useState(0)
    const [rowsPerPage, setRowsPerPage] = useState(pages[page])
    const [order, setOrder] = useState()
    const [orderBy, setOrderBy] = useState()

    const TblContainer = props => (
        <Table className={classes.table}>
            {props.children}
        </Table>
    )

    TblContainer.propTypes = {
        children:PropTypes.element
    }

    const TblHead = props => {

        const handleSortRequest = cellId => {
            const isAsc = orderBy === cellId && order === "asc";
            setOrder(isAsc ? 'desc' : 'asc');
            setOrderBy(cellId)
        }

        return (<TableHead>
            <TableRow>
                {
                    headCells.map(headCell => (
                        <TableCell key={headCell.id}
                            sortDirection={orderBy === headCell.id ? order : false}>
                            {headCell.disableSorting ? headCell.label :
                                <TableSortLabel
                                    active={orderBy === headCell.id}
                                    direction={orderBy === headCell.id ? order : 'asc'}
                                    onClick={() => { handleSortRequest(headCell.id) }}>
                                    {headCell.label}
                                </TableSortLabel>
                            }
                        </TableCell>))
                }
            </TableRow>
        </TableHead>)
    }

    const handleChangePage = (event, newPage) => {
        setPage(newPage);
    }

    const handleChangeRowsPerPage = event => {
        setRowsPerPage(parseInt(event.target.value, 10))
        setPage(0);
    }

    const TblPagination = () => (<TablePagination
        component="div"
        page={page}
        rowsPerPageOptions={pages}
        rowsPerPage={rowsPerPage}
        count={records.length}
        onChangePage={handleChangePage}
        onChangeRowsPerPage={handleChangeRowsPerPage}
    />)

    function stableSort(array, comparator) {
        const stabilizedThis = array.map((el, index) => [el, index]);
        stabilizedThis.sort((a, b) => {
            const order = comparator(a[0], b[0]);
            if (order !== 0) return order;
            return a[1] - b[1];
        });
        return stabilizedThis.map((el) => el[0]);
    }

    function getComparator(order, orderBy) {
        return order === 'desc'
            ? (a, b) => descendingComparator(a, b, orderBy)
            : (a, b) => -descendingComparator(a, b, orderBy);
    }

    function descendingComparator(a, b, orderBy) {
        if (b[orderBy] < a[orderBy]) {
            return -1;
        }
        if (b[orderBy] > a[orderBy]) {
            return 1;
        }
        return 0;
    }

    const recordsAfterPagingAndSorting = () => {
        return stableSort(filterFn.fn(records), getComparator(order, orderBy))
            .slice(page * rowsPerPage, (page + 1) * rowsPerPage)
    }

    return {
        TblContainer,
        TblHead,
        TblPagination,
        recordsAfterPagingAndSorting
    }
}

ParentCoponent

import React,{ useState, useEffect, useCallback } from 'react';
import  { Button, Typography, Grid, TextField, MenuItem, Menu, IconButton, Link, TableBody, TableRow, TableCell, Paper, makeStyles } from '@material-ui/core';
import Modals from './Components/Modals';
import DropDownComponent from './Components/DropDownComponent';
import PropTypes from 'prop-types';
import { fetchImage,postImage, removeImage, updateImageStatus } from './app/ActionCreator';
import { connect } from 'react-redux';
import EditStatusComponent from './Components/EditStatusComponent';
import { Edit, Delete } from '@material-ui/icons';
import TableComponent from './Components/TableComponent';


const styles = makeStyles(theme => ({
    root:{
        marginTop:'60px',
    },
    typo:{
        flexGrow:1,
        textAlign:'center',
        marginTop:'30px',
        color:'black'
    },
    headingpaper:{
        width:'100%',
        height:40,
        marginTop:'30px',
        background:'white',
        borderColor:'white',
    },
    buttonColor:{
        color:'white',
        background:'#143174'
    },
    formControl: {
        margin:'8px',
        minWidth: 120,
    },
    selectEmpty: {
        marginTop: '16px',
    },
    formRoot:{
        margin:'8px',
        width: '25ch',
    },
    modalButton:{
        color:'white',
        background:'#143174',
        marginRight:'10px'
    }
}))

const headCells = [
    {id:1,label:'SlNo'},
    {id:2,label:'Virtual Machine Image '},
    {id:3,label:'createdAt'},
    {id:4,label:'createdBy'},
    {id:5,label:'IpAddress'},
    {id:6,label:'Status'},
    {id:7,label:'Action',disableSorting:true}
]

function AdminImages(props){
    const [imageModal,setImageModal] = useState(false)
    const [name,setName] = useState('')
    const [cpu,setCpu] = useState('')
    const [ram,setRam] = useState('')
    const [disk,setDisk] = useState('')
    const [imageid,setImageid] = useState('')
    const [anchorEl,setAnchorEl] = useState(null)
    const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
           
        
    
    
    const handlecpuChange = (event) => {
       setCpu(event.target.value)
    }
    const handleramChange = (event) => {
       setRam(event.target.value)
    }
    const handlediskChange = (event) => {
        setDisk(event.target.value)   
    }

    useEffect(() => {
        props.fetchImageData()
    },[])


    
    const handleMenu = useCallback((event,id) => {
        setAnchorEl(event.currentTarget)
        setImageid(id)
    },[])

    const handleClose = (ev) => {
        setAnchorEl(null)
        if(ev.target.innerText !== ''){
            const data = {
                "status":ev.target.innerText
            }
            props.updateImageStatusData(imageid,data)
        }
        
    }
    

    const newInstance = () => {
        if(name !== '' && cpu !== '' && ram !== '' && disk !== ''){
            props.postImageData(name,cpu,ram,disk)
        }
        else {
            alert("Please Enter All Field")
        }
        setImageModal(false)
    }

    const labstatus = (status) => {
        if(status === 'Resuming' || status === 'Running'){
            return(
                <EditStatusComponent
                    status={status}
                    statuscolor='#32CD32'
                    showDropDown={false}/>
            )
        }
        else if(status === 'Suspending' || status === 'suspending' || status === 'error'){
            return(
                <EditStatusComponent
                    status={status}
                    statuscolor='red'
                    showDropDown={false}/>
            ) 
        }
        else{
            return(
                <EditStatusComponent
                    status={status}
                    statuscolor='#143174'
                    showDropDown={false}/> 
            )
        }
    } 
    const handleSearch = e => {
        let target = e.target;
        setFilterFn({
            fn: items => {
                if (target.value == "")
                    return items;
                else
                    return items.filter(x => x.instance_name.toLowerCase().includes(target.value))
            }
        })
    }

        const classes = styles()

        const { TblContainer, TblHead, TblPagination, recordsAfterPagingAndSorting } = TableComponent(props.Images.images,headCells,filterFn)
        
        return(
            <div className={classes.root}>
                   <Grid container direction="row" justify="flex-end" alignItems="flex-start">
                       <Button variant="contained" className={classes.buttonColor} onClick={() => setImageModal(true)}>
                           Create Images
                        </Button> 
                   </Grid>
                <Typography variant="h2" noWrap className={classes.typo}>
                    Virtual Machine Image
                </Typography>
                <Paper>
                    <TblContainer>
                        <TblHead/>
                        <TableBody>
                            {
                                recordsAfterPagingAndSorting().map((v,i) => (
                                    <TableRow key={v.id}>
                                        <TableCell>{v.id}</TableCell>
                                        <TableCell>{v.instance_name}</TableCell>
                                        <TableCell>{v.created_at}</TableCell>
                                        <TableCell>{v.created_by.first_name + v.created_by.last_name}</TableCell>
                                        <TableCell><Link target={"_blank"} onClick={() => window.open(`http://${v.ip_address}/project`,'_blank')}>{v.ip_address}</Link></TableCell>
                                        <TableCell>{labstatus(v.status)}</TableCell>
                                        <TableCell>
                                            <div style={{display:'flex'}}>
                                                <IconButton 
                                                    aria-controls="simple-menu" 
                                                    aria-haspopup="true"
                                                    onClick={(e)=> handleMenu(e,v.id)}>
                                                    <Edit/>
                                                </IconButton>   
                                                <IconButton onClick={() => props.removeImageData(v.id)}>
                                                    <Delete/>
                                                </IconButton>
                                            </div> 
                                        </TableCell>
                                    </TableRow>
                                ))
                            }
                        </TableBody>
                    </TblContainer>
                   
                    <TblPagination/>
                  
                </Paper>
                <Menu 
                    id="simple-menu"
                    anchorEl={anchorEl}
                    keepMounted
                    open={Boolean(anchorEl)}
                    onClose={handleClose}>
                    <MenuItem value="Resume" onClick={handleClose}>Resume</MenuItem>
                    <MenuItem value="Suspend" onClick={handleClose}>Suspend</MenuItem>
                    <MenuItem value="Freeze" onClick={handleClose}>Freeze</MenuItem>
                </Menu>    
                <div>
                <Modals 
                    show={imageModal}
                    title="Create Instance"
                    handleClose={() => setImageModal(true)}
                    showSubmit={true}
                    onPress={newInstance}
                    size="xs">
                        <TextField 
                            id="standard-basic" 
                            label="Name"
                            style={{width:'50ch',margin:10}}
                            value={name}
                            onChange={(e) => setName(e.target.value)} 
                            InputLabelProps={{
                                shrink: true,
                            }}/>                 
                        <DropDownComponent
                            dropdownid="standard-select-cpu"
                            selectedValue={cpu}
                            handleChange={handlecpuChange}
                            dropdownLabel="CPU">
                            <MenuItem value="8">8</MenuItem>
                            <MenuItem value="10">10</MenuItem>
                            <MenuItem value="12">12</MenuItem>    
                            <MenuItem value="14">14</MenuItem>
                            <MenuItem value="16">16</MenuItem>
                            <MenuItem value="18">18</MenuItem>
                            <MenuItem value="20">20</MenuItem>
                            <MenuItem value="22">22</MenuItem>
                            <MenuItem value="24">24</MenuItem>
                            <MenuItem value="26">26</MenuItem>
                            <MenuItem value="28">28</MenuItem>
                            <MenuItem value="30">30</MenuItem>
                            <MenuItem value="32">32</MenuItem>             
                        </DropDownComponent>
                        <DropDownComponent
                            dropdownid="standard-select-ram"
                            selectedValue={ram}
                            handleChange={handleramChange}
                            dropdownLabel="RAM">
                            <MenuItem value="16">16</MenuItem>
                            <MenuItem value="20">20</MenuItem>
                            <MenuItem value="24">24</MenuItem>    
                            <MenuItem value="28">28</MenuItem>
                            <MenuItem value="32">32</MenuItem>
                            <MenuItem value="36">36</MenuItem>
                            <MenuItem value="40">40</MenuItem>
                            <MenuItem value="44">44</MenuItem>
                            <MenuItem value="48">48</MenuItem>
                            <MenuItem value="52">52</MenuItem>
                            <MenuItem value="56">56</MenuItem>
                            <MenuItem value="60">60</MenuItem>
                            <MenuItem value="64">64</MenuItem>             
                        </DropDownComponent>
                        <DropDownComponent
                            dropdownid="standard-select-disk"
                            selectedValue={disk}
                            handleChange={handlediskChange}
                            dropdownLabel="Disk">
                            <MenuItem value="50">50</MenuItem>
                            <MenuItem value="100">100</MenuItem>
                            <MenuItem value="150">150</MenuItem>    
                            <MenuItem value="200">200</MenuItem>
                            <MenuItem value="250">250</MenuItem>
                            <MenuItem value="300">300</MenuItem>
                            <MenuItem value="350">350</MenuItem>
                            <MenuItem value="400">400</MenuItem>
                            <MenuItem value="450">450</MenuItem>
                            <MenuItem value="500">500</MenuItem>            
                        </DropDownComponent>    
                    </Modals>
                </div> 
            </div>
        )
    
}

const mapStateToProps = state => {
    return {
        Images:state.Images,
    }
}

const mapDispatchToProps = dispatch => ({
    fetchImageData:() => {
        dispatch(fetchImage())
    },
    postImageData:(name,cpu,ram,storage) => {
        dispatch(postImage(name,cpu,ram,storage))
    },
    removeImageData:(id) => {
        dispatch(removeImage(id))
    },
    updateImageStatusData:(id,data) => {
        dispatch(updateImageStatus(id,data))
    }
})

AdminImages.propTypes = {
    classes:PropTypes.object.isRequired,
    Images:PropTypes.object,
    fetchImageData:PropTypes.func,
    postImageData:PropTypes.func,
    removeImageData:PropTypes.func,
    updateImageStatusData:PropTypes.func,
}

export default connect(mapStateToProps,mapDispatchToProps)(AdminImages)

Upvotes: 0

Views: 103

Answers (1)

sathya reddy
sathya reddy

Reputation: 777

You can prevent unnecessary re-renders of a child component in 2 ways

  1. If your component is class based then you can extend React.PureComponent or implement shouldComponentUpdate lifecycle method by yourself.
class MyComponent extends React.PureComponent {
    // your component logic
}
class MyComponent extends Component {

    shouldComponentUpdate(nextProps, nextState) {
          //compare nextProps and this.props
          //compare nextState and this.state
          //return true for re-render, otherwise false
    }
    // your component logic

}

  1. If you have a functional component you can wrap it with memo function that will check only for prop changes or you can pass it a function in the second argument that will do the current props and next props comparison and return true/false if you want do manual comparison just like shouldComponentUpdate
const MyComponent = React.memo(function(props) {
  /* render using props */
});
function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

Here are some useful links from the docs:

  1. class component
  2. function component

Upvotes: 1

Related Questions