I am having with Re-render with parent functional component. My update and delete functions are handled in Redux after success execution does not re-render parent functional component
***This my Main component where i am getting clients from Api USing Redux it is working working
import React, { Fragment,useEffect } from 'react'
import {connect} from 'react-redux';
import {getClients,deleteClient} from '../Redux//actions/clientActions';
import {Spin,Table,Tag,Button} from 'antd';
import { Link } from 'react-router-dom';
import Moment from 'react-moment'
const Clients =({getClients,deleteClient,client:{clients,loading},history})=> {
useEffect(()=>{
getClients()
}
,[getClients]);
const columns = [
{
title:'Company',
dataIndex: 'companyName',
key: 'companyName',
render: text => <a>{text}</a>,
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
},
{
title: 'Price Type',
dataIndex: 'typePrice',
key: 'typePrice',
},
{
title: 'Partita Iva',
dataIndex: 'partitaIva',
key: 'partitaIva',
},
{
title: 'Codice Fiscale',
dataIndex: 'codiceFiscale',
key: 'codiceFiscale',
},
{
title: 'Name',
dataIndex: 'name',
key: '1',
},
{
title: 'SurName',
dataIndex: 'surname',
key: '2',
},
{
title: 'Address',
dataIndex: 'address',
key: '3',
},
{
title: 'City',
dataIndex: 'city',
key: '3',
},
{
title: 'Zip',
dataIndex: 'zip',
key: '4',
},
{
title: 'Country',
dataIndex: 'country',
key: '5',
},
{
title: 'Phone',
dataIndex: 'phone',
key: '6',
},
{
title: 'Email',
dataIndex: 'email',
key: '7',
},
{
title: 'Domains',
key: 'domains',
dataIndex: 'domains',
render: domains => (
<span>
{domains&&domains.map(domain => {
let color = domain.length > 5 ? 'geekblue' : 'green';
return (
<Tag color={color} key={domain}>
{domain.toUpperCase()}
</Tag>
);
})}
</span>
),
},
{
title: 'Notes',
dataIndex: 'notes',
key: 'notes',
},
{
title: 'Creator Id',
dataIndex: 'createdByID',
key: 'createdByID',
},
{
title: 'Date',
dataIndex: 'date',
key: 'date',
render:(text)=>(
<>
<Moment format='YYYY/MM/DD'>{text}</Moment>
</>
)
},
{
title: '',
render:(record)=>(
<>
<Button style={{
marginBottom:"5px"
}} type="primary" ghost>
<Link to={`/clients/update/${record._id}`}>Update</Link>
</Button>
<Button type="primary" style={{
marginBottom:"5px"
}} ghost>
<Link to={`/clients/${record._id}`}>Detail</Link>
</Button>
</>
)
},
];
return loading && clients.length === 0 ? (
<div style={{
position:"absolute",
left:"50%",
top:"50%",
traverse: 'translate(-50%, -50%)'
}}>
<Spin/>
</div>
):
(
<Fragment>
<Table pagination={{ defaultPageSize: 4, pageSizeOptions: ['10', '20', '30']}} columns={columns} dataSource={clients} scroll={{ x: 1300 }}/>
</Fragment>
)
}
const mapStateToProps = state =>{
return {
client:state.client
}
}
export default connect(mapStateToProps,{getClients,deleteClient})(Clients);
***This is my Detail Page and i am Implementing Delete Function function executes perfect and goes to main client but Clients Does not Update on Main Page After Deletion
import React, { Fragment,useEffect,useState } from 'react'
import {connect} from 'react-redux';
import {withRouter,useParams,useHistory,Redirect} from 'react-router-dom';
import {getClient,deleteClient} from '../Redux/actions/clientActions'
import { Card,Spin,Button,Modal } from 'antd';
const gridStyle = {
width: '33%',
textAlign: 'center',
};
const ClientDetail =({client:{client},getClient,deleteClient,loading,history})=> {
let [visible,setVisible] = useState(false)
let {id} = useParams()
useEffect(()=>{
getClient(id)
},[getClient,id])
const handleDelete=()=>{
deleteClient(id)
setVisible(true)
}
const handleOk = () => {
setVisible(false)
history.push('/clients')
};
return loading && client === null? (
<div style={{
position:"absolute",
left:"50%",
top:"50%",
traverse: 'translate(-50%, -50%)'
}}>
<Spin/>
</div>
): (
<Fragment>
<Modal
title="Confirmation Dailog"
visible={visible}
// onOk={handleOk}
footer={[
<Button key="back" onClick={handleOk}>
OK
</Button>,
]}
>
<p>Client Has been Deleted Successfully...</p>
</Modal>
<Card style={{textAlign:"center",
marginTop:"2rem"
}} title="Client Detail">
<Card.Grid style={gridStyle}>CompanyName: {client&&client.companyName}</Card.Grid>
<Card.Grid style={gridStyle}>Type:{client&&client.type}</Card.Grid>
<Card.Grid style={gridStyle}>PriceType:{client&&client.typePrice}</Card.Grid>
<Card.Grid style={gridStyle}>PartitaIva:{client&&client.partitaIva}</Card.Grid>
<Card.Grid style={gridStyle}>CodiceFiscale:{client&&client.codiceFiscale}</Card.Grid>
<Card.Grid style={gridStyle}>Name:{client&&client.name}</Card.Grid>
<Card.Grid style={gridStyle}>SurName:{client&&client.surname}</Card.Grid>
<Card.Grid style={gridStyle}>Address:{client&&client.address}</Card.Grid>
<Card.Grid style={gridStyle}>City:{client&&client.city}</Card.Grid>
<Card.Grid style={gridStyle}>Zip:{client&&client.zip}</Card.Grid>
<Card.Grid style={gridStyle}>Country:{client&&client.country}</Card.Grid>
<Card.Grid style={gridStyle}>Phone:{client&&client.phone}</Card.Grid>
<Card.Grid style={gridStyle}>Email:{client&&client.email}</Card.Grid>
<Card.Grid style={gridStyle}>Domains1:{client&&client.domains[0]}</Card.Grid>
<Card.Grid style={gridStyle}>Domains2:{client&&client.domains[1]}</Card.Grid>
<Card.Grid style={gridStyle}>Domains3:{client&&client.domains[2]}</Card.Grid>
<Card.Grid style={gridStyle}>Notes:{client&&client.notes}</Card.Grid>
<Card.Grid style={gridStyle}>CreatorID:{client&&client.createdByID}</Card.Grid>
<Button type="danger" onClick={handleDelete} style={{ width: '40%',marginTop:"1rem",marginBottom:"1rem"}}>Delete</Button>
</Card>
</Fragment>
)
}
const mapStateToProps=state=>({
client:state.client
})
export default connect(mapStateToProps,{getClient,deleteClient}) (withRouter(ClientDetail));
Here's an example with button and OnClick below that you can use for your work logic. I hope it helps you.
...
const ExampleComponent = props => {
useEffect(()=>{
getClients()
}
,[getClients]);
const handleDelete = () => {
const {asyncDelete, id, history} = props;
asyncDelete({id, onSuccess: () => {
getClients();
// or history.push('/');
}
});
}
return(
<>
...
<button onClick={handleDelete}
...
</>
)
}
const mapDispatchToProps = dispatch => ({
asyncDelete: ({ id, onSuccess }) => dispatch(asyncDelete({ id, onSuccess }))
});
...
Assuming the reudx-thunk and axios in actions.js file
...
export const asyncDelete = ({ id, onSuccess }) => {
return dispatch => {
...
axios
.delete(`/user/${id}`)
.then(response => {
...
onSuccess();
})
.catch(error => console.log(error));
};
};
...
I will pass a onSuccess() method to the async action, when the deletion is done and if the deletion is done correctly, that method will also be executed and the component will be updated again. You can apply this logic to the Nested Routes as well, though there are different ways one of them can be the same logic.
Related
Hi I am trying to build a handleChange method that will not only change the value of a dropdown but also dispatch a redux action which we need. Unfortunately the form is built with formik so formik.handleChange is being used. Below Im attaching two code snippets -- a parent component from which the formik value is coming and a child component where its being used.
Parent Component
/* eslint-disable no-nested-ternary */
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Grid, Typography, IconButton } from '#mui/material';
import DoNotDisturbIcon from '#mui/icons-material/DoNotDisturb';
import * as Yup from 'yup';
import { useTranslation } from 'react-i18next';
import { useFormik } from 'formik';
import { useDispatch, useSelector } from 'react-redux';
import { NavigateRoutes } from '../../../constant';
import SwapCourseLocation from '../../admin-user/swap-course-location';
import { DialogAtom } from '../../../components/atoms';
import { getFormattedDate } from '../../../utils/methods';
import styles from './style';
import useStyles from '../../../custom-hooks/useStyles';
import Loader from '../../../components/atoms/loader';
import DataGridProTable from '../../../components/atoms/data-grid-pro';
import { Student, SwapCourse } from './helper';
import StudentHeader from './header';
import { SwapCourseDialogFooter } from '../../admin-user/students/helper';
import { SwapIcon, MailIcon } from '../../../assets/svg';
// import { getAllCourseService } from '../../../store/services/auth';
import Constant from '../../../store/constant';
import useLocationCoordinator from '../../../custom-hooks/useLocationCoordinator';
import { setLocalStorage } from '../../../utils/localStorageMethod';
import CommonProfileImage from '../../../components/molecules/common/CommonProfileImage';
import useStudent from '../../../custom-hooks/useStudent';
// import useStudent from '../../../custom-hooks/useStudent';
export default function StudentList({
setDialogOpen,
setLoading,
refreshList,
loading,
visibleFields,
fileHeaders,
// marksOpen,
setCourseData,
courseData,
setOpenDialog,
}) {
const classes = useStyles(styles)();
const { t } = useTranslation();
const [selectedRows, setSelectedRows] = React.useState([]);
const validationSchema = Yup.object({});
const coordinatorInfo = useLocationCoordinator();
const students = coordinatorInfo?.students;
const assignedLocations = coordinatorInfo?.assignedLocations;
const assignedYears = coordinatorInfo?.assignedYears;
const [rowData, setRowData] = useState([]);
const [studentData, setStudentData] = useState([]);
const [gridLoading, setGridLoading] = useState(true);
const [locations, setLocationData] = useState([]);
const [years, setYearsData] = useState([]);
const [fileName, setFileName] = useState(false);
const [error, setError] = useState('');
const [customForm, setCustomForm] = useState('');
const [isSwapCourseDialog, setSwapCourseOrLocation] = useState(false);
const locationCoordinatorData = useSelector((state) => state?.getLocationCoordinator);
const selectedYearRedux = locationCoordinatorData?.selectedYear;
const selectedLocationRedux = locationCoordinatorData?.selectedLocations;
const [swapCourseLocationInfo, setCourseLocationInfo] = useState({
studentId: '',
studentName: '',
acedemicYear: '',
courseFrom: '',
courseTo: '',
locationFrom: '',
locationTo: '',
sectionFrom: '',
sectionTo: '',
changeLogs: '',
});
const [disableMovestudent, setDisableMovestudent] = useState(true);
const studentInfo = useStudent();
const dispatch = useDispatch();
const navigate = useNavigate();
// useEffect(() => {
// console.log(selectedLocationRedux?.value);
// }, [selectedLocationRedux]);
const formik = useFormik({
initialValues: {
academicYear: years[0]?.id,
locationId: locations[0]?.id,
courseId: courseData[0]?.id,
},
validationSchema,
onSubmit: () => {
setLoading(true);
},
});
useEffect(() => {
setLocalStorage('locationDashboard', false);
setStudentData(students);
}, [students]);
useEffect(() => {
return () => {
setStudentData([]);
setRowData([]);
};
}, []);
useEffect(() => {
setLocationData(assignedLocations);
setYearsData(assignedYears);
if (selectedLocationRedux?.value) {
formik.setFieldValue('locationId', selectedLocationRedux?.value);
} else {
formik.setFieldValue('locationId', assignedLocations[0]?.id);
}
const currentYear = new Date().getFullYear();
const selectedYear = assignedYears.filter((opt) => opt?.id.substring(0, 4) === currentYear.toString());
if (selectedYearRedux?.id) {
formik.setFieldValue('academicYear', selectedYearRedux?.id);
} else {
formik.setFieldValue('academicYear', selectedYear[0]?.id);
}
// formik.setFieldValue('academicYear', selectedYear[0]?.id);
}, [assignedLocations, assignedYears]);
useEffect(() => {
setCourseData(studentInfo?.courses);
}, [studentInfo?.courses]);
useEffect(() => {
setError('');
setGridLoading(true);
setSelectedRows([]);
const locData = assignedLocations?.filter(
(i) => i?.id === formik.values.locationId,
);
const date = getFormattedDate(new Date());
const file = `${locData[0]?.name}-${formik.values.academicYear}-${date}`;
setFileName(file);
const payload = {
locationId: formik?.values?.locationId,
academicYear: formik?.values?.academicYear,
courseId: formik?.values?.courseId,
};
if (payload?.locationId && payload?.academicYear && payload?.courseId && formik?.values?.courseId?.[0] !== undefined) {
refreshList(payload);
}
}, [
formik?.values?.locationId,
formik?.values?.academicYear,
formik?.values?.courseId,
]);
useEffect(() => {
const data = new Student(studentData);
setRowData(data);
setGridLoading(loading);
}, [studentData]);
const getMarksHeader = () => (
<div>
<span style={{ height: '5vh' }}>
{t('MARKS')}
</span>
<div>{t('Q2')}</div>
</div>
);
// const getBonusHeader = () => (
// <div>
// <span>
// {t('BONUS_MARKS')}
// </span>
// {/* <div>{t('Marks')}</div> */}
// </div>
// );
const getNewReturningHeader = () => (
<div>
<span style={{ height: '5vh' }}>
{t('New / ')}
</span>
<div>{t('Returning')}</div>
</div>
);
const getHomeworkHeader = () => (
<div>
<span style={{ height: '5vh', left: -14 }}>
{t('HOMEWORK')}
</span>
<div>{t('Q2')}</div>
</div>
);
const viewLogs = (onClose) => {
onClose(false);
navigate(NavigateRoutes.STUDENTS_LOGS);
};
const handleMoveStudent = () => {
customForm.handleSubmit();
};
const refreshStudentData = () => {
setLoading(true);
const payload = {
locationId: formik?.values?.locationId,
academicYear: formik?.values?.academicYear,
courseId: formik?.values?.courseId,
};
// setTimeout(() => {
refreshList(payload);
// }, 1000);
};
const swapCourseDialogFooter = (
<SwapCourseDialogFooter
classes={classes}
t={t}
primaryHandle={handleMoveStudent}
secHandle={() => setSwapCourseOrLocation(false)}
viewLogs={viewLogs}
disableMovestudent={disableMovestudent}
/>
);
const isChangeLogVisible = false;
const swapCourseLocation = (
<SwapCourseLocation
refreshStudentsData={refreshStudentData}
{...{
setCourseLocationInfo,
swapCourseLocationInfo,
setCustomForm,
setSwapCourseOrLocation,
courseData,
isChangeLogVisible,
setDisableMovestudent,
}}
/>
);
const [columns, setColumns] = useState();
useEffect(() => {
const columnsData = [
{
field: 'profilePhoto',
headerName: t('PICTURES'),
sortable: false,
hide: !visibleFields?.includes(t('PICTURES')),
renderCell: (rowInfo) => (
<CommonProfileImage
key={rowInfo?.id}
src={rowInfo?.row?.studentInfo?.profilePhoto}
/>
),
headerClassName: 'pictureHeader',
cellClassName: 'pictureCell',
},
{
field: 'studentName',
headerName: t('STUDENT_NAME'),
hide: !visibleFields?.includes(t('STUDENT_NAME')),
align: 'left',
headerClassName: 'studentNameHeader',
cellClassName: 'studentNameCell',
},
{
field: 'parentName',
headerName: t('PARENT_NAME'),
hide: !visibleFields?.includes(t('PARENT_NAME')),
align: 'left',
headerClassName: 'commonHeader',
cellClassName: 'commonCell',
},
{
field: 'phoneNumber',
hide: !visibleFields?.includes(t('CONTACT_NO')),
headerName: t('CONTACT_NO'),
align: 'left',
headerClassName: 'phoneNumberHeader',
cellClassName: 'phoneNumberCell',
},
{
field: 'course',
hide: !visibleFields?.includes(t('COURSE')),
headerName: t('COURSE'),
align: 'left',
headerClassName: 'courseHeader',
cellClassName: 'courseCell',
},
{
field: 'section',
hide: !visibleFields?.includes(t('SECTION')),
headerName: t('SECTION'),
align: 'left',
headerClassName: 'courseHeader',
cellClassName: 'courseCell',
},
{
field: 'newReturning',
hide: !visibleFields?.includes(t('NEW_RETURNING')),
headerName: getNewReturningHeader(),
align: 'left',
headerClassName: 'newReturningHeader',
cellClassName: 'newReturningCell',
},
{
field: 'marksQ1',
disableColumnResize: 'false',
hide: !visibleFields?.includes(t('MARKS')),
sortable: false,
headerName: t('Q1'),
align: 'center',
headerClassName: 'marksHeader',
cellClassName: 'marksCell',
renderCell: (cellValues) => (
<span
className={cellValues?.row?.marksQ1 >= cellValues?.row?.Q1PassingCriteria ? classes.marksGreen
: cellValues?.row?.marksQ1 < (parseInt(cellValues?.row?.Q1PassingCriteria, 10) - 10) ? classes.marksRed
: cellValues?.row?.marksQ1 >= (parseInt(cellValues?.row?.Q1PassingCriteria, 10) - 10) ? classes.marksYellow : classes.marksGrey}
onClick={(e) => setOpenDialog(e, cellValues, 'marks', 'Q1')}
>
{cellValues?.row?.Q1PassingCriteria ? Number.isInteger(cellValues?.row?.marksQ1) ? cellValues?.row?.marksQ1 : Number(cellValues?.row?.marksQ1).toFixed(2) : '-'}
{cellValues?.row?.Q1PassingCriteria && !cellValues?.row?.Q1Attended ? '(A)' : ''}
</span>
),
},
{
field: 'marksQ2',
hide: !visibleFields?.includes(t('MARKS')),
headerName: getMarksHeader(),
disableColumnResize: 'false',
align: 'left',
headerClassName: 'marksHeaderSpan',
cellClassName: 'marksQ3Cell',
sortable: false,
renderCell: (cellValues) => (
<span
className={cellValues?.row?.marksQ2 >= cellValues?.row?.Q2PassingCriteria ? classes.marksGreen
: cellValues?.row?.marksQ2 < (parseInt(cellValues?.row?.Q2PassingCriteria, 10) - 10)
? classes.marksRed : cellValues?.row?.marksQ2 >= (parseInt(cellValues?.row?.Q2PassingCriteria, 10) - 10) ? classes.marksYellow : classes.marksGrey}
onClick={(e) => setOpenDialog(e, cellValues, 'marks', 'Q2')}
>
{cellValues?.row?.Q2PassingCriteria
? Number.isInteger(cellValues?.row?.marksQ2) ? cellValues?.row?.marksQ2 : Number(cellValues?.row?.marksQ2).toFixed(2) : '-'}
{cellValues?.row?.Q2PassingCriteria && !cellValues?.row?.Q2Attended ? '(A)' : ''}
</span>
),
},
{
field: 'marksQ3',
hide: !visibleFields?.includes(t('MARKS')),
headerName: t('Q3'),
headerClassName: 'marksQ3Header',
cellClassName: 'marksQ3Cell',
sortable: false,
renderCell: (cellValues) => (
<span
className={cellValues?.row?.marksQ3 >= cellValues?.row?.Q3PassingCriteria ? classes.marksGreen
: cellValues?.row?.marksQ3 < (parseInt(cellValues?.row?.Q3PassingCriteria, 10) - 10)
? classes.marksRed : cellValues?.row?.marksQ3 >= (parseInt(cellValues?.row?.Q3PassingCriteria, 10) - 10) ? classes.marksYellow : classes.marksGrey}
onClick={(e) => setOpenDialog(e, cellValues, 'marks', 'Q3')}
>
{cellValues?.row?.Q3PassingCriteria
? Number.isInteger(cellValues?.row?.marksQ3) ? cellValues?.row?.marksQ3 : Number(cellValues?.row?.marksQ3).toFixed(2) : '-'}
{cellValues?.row?.Q3PassingCriteria && !cellValues?.row?.Q3Attended ? '(A)' : ''}
</span>
),
},
{
field: 'homeworkQ1',
hide: !visibleFields?.includes(t('HOMEWORK')),
headerName: t('Q1'),
align: 'center',
headerClassName: 'marksHeader',
cellClassName: 'marksCell',
sortable: false,
renderCell: (cellValues) => (
<span
className={cellValues?.row?.Q1HomeWorkWeightage
? classes.homeworkColor : classes.marksGrey}
onClick={(e) => setOpenDialog(e, cellValues, 'homework-marks', 'Q1')}
>
{cellValues?.row?.Q1HomeWorkWeightage
? Number.isInteger(cellValues?.row?.homeworkQ1) ? cellValues?.row?.homeworkQ1 : Number(cellValues?.row?.homeworkQ1).toFixed(2) : '-'}
</span>
),
},
{
field: 'homeworkQ2',
hide: !visibleFields?.includes(t('HOMEWORK')),
headerName: getHomeworkHeader(),
align: 'left',
headerClassName: 'homeworkHeaderSpan',
cellClassName: 'marksQ3Cell',
sortable: false,
renderCell: (cellValues) => (
<span
className={cellValues?.row?.Q2HomeWorkWeightage
? classes.homeworkColor : classes.marksGrey}
onClick={(e) => setOpenDialog(e, cellValues, 'homework-marks', 'Q2')}
>
{cellValues?.row?.Q2HomeWorkWeightage
? Number.isInteger(cellValues?.row?.homeworkQ2) ? cellValues?.row?.homeworkQ2 : Number(cellValues?.row?.homeworkQ2).toFixed(2) : '-'}
</span>
),
},
{
field: 'homeworkQ3',
hide: !visibleFields?.includes(t('HOMEWORK')),
sortable: false,
headerName: t('Q3'),
align: 'left',
headerClassName: 'marksQ3Header',
cellClassName: 'marksQ3Cell',
renderCell: (cellValues) => (
<span
className={cellValues?.row?.Q3HomeWorkWeightage
? classes.homeworkColor : classes.marksGrey}
onClick={(e) => setOpenDialog(e, cellValues, 'homework-marks', 'Q3')}
>
{cellValues?.row?.Q3HomeWorkWeightage
? Number.isInteger(cellValues?.row?.homeworkQ3) ? cellValues?.row?.homeworkQ3 : Number(cellValues?.row?.homeworkQ3).toFixed(2) : '-'}
</span>
),
},
{
field: 'bonus',
hide: !visibleFields?.includes(t('BONUS_MARKS')),
sortable: false,
headerName: t('BONUS_MARKS'),
headerClassName: 'bonusHeaderSpan',
cellClassName: 'bonusCell',
renderCell: (cellValues) => (
<span
className={classes.marksGreen}
onClick={(e) => setOpenDialog(e, cellValues, 'bonus', null)}
>
{cellValues?.row?.bonus || 0}
</span>
),
},
{
field: 'grade',
hide: !visibleFields?.includes(t('GRADE')),
sortable: false,
headerName: t('GRADE'),
// align: 'left',
headerClassName: 'gradeHeader',
cellClassName: 'gradeCell',
},
{
field: 'gpa',
hide: !visibleFields?.includes(t('GPA')),
sortable: false,
headerName: t('GPA'),
// align: 'left',
headerClassName: 'gradeHeader',
cellClassName: 'gradeCell',
},
{
field: 'annualScore',
hide: !visibleFields?.includes(t('ANNUAL_SCORE')),
headerName: t('ANNUAL_SCORE'),
align: 'left',
headerClassName: 'commonHeader',
cellClassName: 'commonCell',
},
{
field: 'actions',
hide: !visibleFields?.includes(t('ACTIONS')),
headerName: t('ACTIONS'),
sortable: false,
headerAlign: 'left',
align: 'left',
headerClassName: 'commonHeader',
cellClassName: 'actionCells',
renderCell: (row) => (
<span style={{ display: 'flex' }}>
<span
style={{ paddingRight: 5 }}
>
<IconButton onClick={() => {
const data = new SwapCourse(row);
setCourseLocationInfo(data);
setSwapCourseOrLocation(true);
}}
>
<SwapIcon />
</IconButton>
</span>
<span>
<IconButton
onClick={() => {
const { manabadiEmail } = row.row.studentInfo;
const emailParents = [];
emailParents.push(row?.row?.parent1Info?.personalEmail?.toString());
emailParents.push(row?.row?.parent2Info?.personalEmail?.toString());
dispatch({ type: Constant.SET_MAIL_SUBJECT, payload: '' });
dispatch({ type: Constant.SET_MAIL_BODY, payload: '' });
dispatch({ type: Constant.RECIPIENTS, payload: [manabadiEmail] });
dispatch({ type: Constant.MAIL_FILTER, payload: 'Student' });
dispatch({ type: Constant.MAIL_PARENTS, payload: emailParents });
setLocalStorage('selectedLocation', row.row.enrolled_courses[0].location.id);
setLocalStorage('selectedYear', row.row.enrolled_courses[0].academicYear);
setLocalStorage('showLocationFilterRecipients', false);
setLocalStorage('showLocationAnnouncementsRecipients', false);
setLocalStorage('showSelectAllinEmail', false);
navigate(NavigateRoutes.LOCATION_COORDINATOR_EMAIL);
}}
>
<MailIcon />
</IconButton>
</span>
</span>
),
},
];
setColumns(columnsData);
}, [visibleFields]);
const showDetail = () => {
if (gridLoading || loading) {
return (
<Grid>
<Loader message={t('LOADING')} />
</Grid>
);
}
if (studentData?.length > 0) {
return (
<Grid xs={12} md={12} mt={5} className={classes.studentsList}>
<DataGridProTable
data={rowData}
columns={columns}
autoHeight
hideFooter
disableColumnFilter
disableColumnSelector
checkboxSelection
disableColumnMenu
disableColumnResize
disableSelectionOnClick
ColumnUnsorted
ColumnSortedAscending
ColumnSortedDescending
onSelectionModelChange={(ids) => {
setError('');
const selectedIDs = new Set(ids);
const selectedData = rowData.filter((row) => selectedIDs.has(row.id));
setSelectedRows(selectedData);
}}
loading={gridLoading}
/>
</Grid>
);
}
if (studentData?.length === 0) {
return (
<Grid container style={{ textAlign: 'center', height: '5vw', marginTop: '7vw' }} className={classes.noData}>
<Grid item xs={12} justifyContent="center">
<DoNotDisturbIcon />
</Grid>
<Grid item xs={12} justifyContent="center">
<Typography variant="subtitle2" color="text.secondary">
{t('NO_DATA')}
</Typography>
</Grid>
</Grid>
);
}
return null;
};
return (
<Grid container>
<Grid xs={12}>
<StudentHeader
{...{
formik,
locations,
years,
courseData,
t,
rowData,
fileHeaders,
fileName,
setDialogOpen,
selectedRows,
setError,
}}
/>
</Grid>
<Grid container spacing={2} direction="row" display="flex" alignItems="center">
<span className={classes.errorText}>{error}</span>
</Grid>
{showDetail()}
<DialogAtom
secHandle={() => setSwapCourseOrLocation(false)}
isOpen={isSwapCourseDialog}
dialogHeading={t('SWAP_SECTION')}
customClass={classes.swapCourseDialog}
footer={swapCourseDialogFooter}
content={swapCourseLocation}
/>
</Grid>
);
}
The studentHeader component has the dropdown which I need to change and add a customize handleChange method. But since its only using formik.handleChange imma little bit confused.
Child Component -- StudentHeader
import React, { useEffect } from 'react';
import { CSVLink } from 'react-csv';
import { Grid, Tooltip } from '#mui/material';
import FileDownloadOutlinedIcon from '#mui/icons-material/FileDownloadOutlined';
import EmailOutlinedIcon from '#mui/icons-material/EmailOutlined';
import { FormikProvider } from 'formik';
import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { setSelectedYear } from '../../../store/actions/getLocationCoordinator';
import { PerformantDropdown, PerfromantMultiValueDropdown } from '../../../components/atoms';
import styles from './style';
import MapPin from '../../../assets/images/map-pin.png';
import useStyles from '../../../custom-hooks/useStyles';
import { ColumnSelectIcon } from '../../../assets/svg';
import { NavigateRoutes } from '../../../constant';
import Constant from '../../../store/constant';
import { setLocalStorage } from '../../../utils/localStorageMethod';
function StudentHeader({
formik,
t,
fileHeaders,
years,
locations,
courseData,
fileName,
setDialogOpen,
selectedRows,
setError,
}) {
const classes = useStyles(styles)();
const navigate = useNavigate();
const dispatch = useDispatch();
setLocalStorage('selectedLocation', '');
setLocalStorage('selectedYear', '');
// const locationCoordinatorData = useSelector((state) => state?.getLocationCoordinator);
// const selectedYearRedux = locationCoordinatorData?.selectedYear;
// useEffect(() => {
// console.log(selectedYearRedux);
// }, [selectedYearRedux]);
useEffect(() => {
setLocalStorage('locationDashboard', false);
formik.setFieldValue('courseId', [courseData?.[0]?.id]);
}, [courseData]);
const sendBulkEmail = () => {
if (selectedRows.length > 0) {
setError('');
const emailList = [];
const emailParents = [];
selectedRows?.forEach((row) => {
emailList.push(row?.studentInfo?.manabadiEmail);
emailParents.push(row?.parent1Info?.personalEmail?.toString());
emailParents.push(row?.parent2Info?.personalEmail?.toString());
});
dispatch({ type: Constant.SET_MAIL_SUBJECT, payload: '' });
dispatch({ type: Constant.SET_MAIL_BODY, payload: '' });
dispatch({ type: Constant.RECIPIENTS, payload: emailList });
dispatch({ type: Constant.MAIL_PARENTS, payload: emailParents });
dispatch({ type: Constant.MAIL_FILTER, payload: 'Student' });
setLocalStorage('selectedLocation', formik?.values?.locationId);
setLocalStorage('selectedYear', formik?.values?.academicYear);
setLocalStorage('showSelectAllinEmail', false);
if (selectedRows.length > 0) {
setLocalStorage('showLocationFilterRecipients', false);
setLocalStorage('showLocationAnnouncementsRecipients', false);
} else {
setLocalStorage('showLocationFilterRecipients', false);
setLocalStorage('showLocationAnnouncementsRecipients', false);
}
navigate(NavigateRoutes.LOCATION_COORDINATOR_EMAIL);
} else {
setError(t('SELECT_ATLEAST_ONE_STUDENT'));
}
};
return (
<Grid container spacing={2} direction="row" display="flex" alignItems="center">
<FormikProvider value={formik}>
<Grid item xs={4} sm={1.5} className={classes.year}>
<PerformantDropdown
minWidth="100%"
label={t('YEAR')}
labelId={t('YEAR')}
id="academicYear"
name="academicYear"
value={formik.values.academicYear}
handleChange={formik.handleChange}
options={years}
customClass="yearDropdown"
variant="standard"
/>
</Grid>
<Grid item xs={4.5} className={classes.locationDropdown}>
<PerformantDropdown
minWidth="100%"
label={t('LOCATION')}
id="locationId"
name="locationId"
value={formik.values.locationId}
handleChange={formik.handleChange}
options={locations}
customClass="locationDropdown"
variant="standard"
icon={<img src={MapPin} alt="" className={classes.mapPinImg} />}
/>
</Grid>
<Grid item xs={6} sm={3} className={classes.courseDropdown}>
<PerfromantMultiValueDropdown
minWidth="100%"
label={t('COURSE')}
value={formik.values.courseId}
options={courseData}
onChange={formik.handleChange}
id="courseId"
name="courseId"
variant="standard"
customClass="courseDropdown"
/>
</Grid>
<Grid item md={1.4} />
<Grid item xs={0.5} className={classes.gridActions}>
{/* {selectedRows?.length ? ( */}
<Tooltip title={t('DOWNLOAD')} placement="right">
<div className={classes.header}>
<CSVLink
headers={fileHeaders}
data={selectedRows}
filename={`${fileName}.csv`}
target="_blank"
>
<FileDownloadOutlinedIcon />
</CSVLink>
</div>
</Tooltip>
{/* ) : null} */}
</Grid>
<Grid item xs={0.5} className={classes.gridActions} onClick={sendBulkEmail}>
<Tooltip title={t('SEND_EMAIL')} placement="right">
<div className={classes.header}>
<EmailOutlinedIcon className={classes.header} />
</div>
</Tooltip>
</Grid>
<Grid
item
xs={0.5}
onClick={() => setDialogOpen(true)}
className={classes.gridActions}
>
{' '}
<Tooltip title={t('UPDATE_SETTINGS')} placement="right">
<div>
<ColumnSelectIcon
customClass={classes.columnSelectionIcon}
strokeColor="#104F96"
/>
</div>
</Tooltip>
</Grid>
</FormikProvider>
</Grid>
);
}
export default StudentHeader;
These two PerformatDropdown needs to be changed. I want to have a handleChange method which will change the value and dispatch a redux action which will store the changed value. The redux part I already solved, please tell me how can change the formik.handleChange??
or we could do this
const handleYearChange = (e) => {
formik.setFieldValue('academicYear', e.target.value);
const payload = {
id: e.target.value,
name: e.target.value,
};
dispatch(setSelectedYear(payload));
};
<PerformantDropdown
minWidth="100%"
label={t('YEAR')}
labelId={t('YEAR')}
id="academicYear"
name="academicYear"
value={formik.values.academicYear}
handleChange={(e) => handleYearChange(e)}
options={years}
customClass="yearDropdown"
variant="standard"
/>
I'm using function component and to draw the table I'm using ant-design. Now I want to do column search for each column present in table
I tried this
https://ant.design/components/table/
It's a class component I got bit confused. Can anyone suggest how to search column level search in ant design table using function component?
Thanks
just to complement the previous answer: searchInput is a resp, in class component it is automatic but in function you have to do the following:
....
const searchInput = useRef(null);
....
<Input
ref={searchInput}
....
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => searchInput && searchInput.current && searchInput.current.select())
}
}
.....
It should be this way, sorry if i forgot to replace a this. I followed the antd table API here https://ant.design/components/table/#components-table-demo-custom-filter-panel
const { Table, Input, Button, Space } = antd;
import Highlighter from 'react-highlight-words';
const { SearchOutlined } = icons;
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Joe Black',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Jim Green',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
const App = () => {
const [state, setState] = useState({searchText:'', searchedColumn:''})
const handleSearch = (selectedKeys, confirm, dataIndex) => {
confirm();
setState({
searchText: selectedKeys[0],
searchedColumn: dataIndex,
});
};
const handleReset = clearFilters => {
clearFilters();
setState({ searchText: '' });
};
const getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
style={{ marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
<Button
type="link"
size="small"
onClick={() => {
confirm({ closeDropdown: false });
setState({
searchText: selectedKeys[0],
searchedColumn: dataIndex,
});
}}
>
Filter
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex]
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
: '',
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => searchInput.select(), 100);
}
},
render: text =>
state.searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[state.searchText]}
autoEscape
textToHighlight={text ? text.toString() : ''}
/>
) : (
text
),
});
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '30%',
...getColumnSearchProps('name'),
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: '20%',
...getColumnSearchProps('age'),
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
...getColumnSearchProps('address'),
sorter: (a, b) => a.address.length - b.address.length,
sortDirections: ['descend', 'ascend'],
},
];
return <Table columns={columns} dataSource={data} />;
}
ReactDOM.render(<App />, mountNode);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
i'm using the following ant component : https://ant.design/components/table/
and i'm really struggling to add the following search option in my table :
here's the table i've created so far :
i'm pretty new to both React and Typescript , since the project is not in js but in typescript i cannot get my code to work properly looking at ant documentation ; i've turned my component to a functional component as i'm gonna be using hooks and added some code to get out most of the compiler errors (type declaration for ts) , still have some syntax errors :
heres my code:
import React, { Component, useState } from 'react';
import {connect} from "react-redux";
// #ts-ignore
import Highlighter from 'react-highlight-words';
//**External Libraries */
//REACT MOSAIC
import {ExpandButton, MosaicWindow, RemoveButton} from "react-mosaic-component";
import {MosaicBranch} from "react-mosaic-component/src/types";
//BLUEPRINTJS
import {InputGroup} from "#blueprintjs/core";
//ANTDESIGN
import { Table , Button ,Tag , Input , Space} from "antd";
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import {CaretRightOutlined , SearchOutlined} from '#ant-design/icons';
//STYLES
import './styles.css'
//API
import {useGetAllUtenti} from '../../../api/index';
import { Utente } from '../../../api/types';
const UserSummaryWindow: React.FC<any> = (props) => {
//HOOKS STATE FOR TABLE SEARCH
const [searchText , setSearchText] = useState('');
const [searchedColumn , setSearchedColumn] = useState('');
const {path} = props;
const dataSource: Object | any = useGetAllUtenti().data;
const ruoli: any = [
{
text: 'User',
value: 'user',
},
{
text: 'Administrator',
value: 'administrator',
},
{
text: 'NumeroVerde',
value: 'numeroVerde',
},
]
const stati: any = [
{
text: 'Attivo',
value: 1,
},
{
text: 'Non Attivo',
value: 0,
}
]
const columns: any = [
{
title: 'Nome',
dataIndex: 'nome',
key: 'nome',
defaultSortOrder: 'descend',
sorter: (a:any, b:any) => { return a.nome.localeCompare(b.nome)},
},
{
title: 'Cognome',
dataIndex: 'cognome',
key: 'cognome',
sorter: (a:any, b:any) => { return a.cognome.localeCompare(b.cognome)},
},
{
title: 'Ruolo',
dataIndex: 'ruolo',
key: 'ruolo',
filters: ruoli,
onFilter: (value:any, record:any) => record.ruolo.indexOf(value) === 0,
sorter: (a:any, b:any) => { return a.ruolo.localeCompare(b.ruolo)},
render: (text: string) => (
<>
{
<Tag color={renderTagColor(text)}>
{text.toUpperCase()}
</Tag>
}
</>)
},
{
title: 'Gestore',
dataIndex: 'gestore',
key: 'gestore',
sorter: (a:any, b:any) => { return a.gestore.localeCompare(b.gestore)},
},
{
title: 'Username',
dataIndex: 'username',
key: 'username',
sorter: (a:any, b:any) => { return a.username.localeCompare(b.username)},
},
// {
// title: 'Password',
// dataIndex: 'password',
// key: 'password',
// },
// {
// title: 'IDEnte',
// dataIndex: 'idEnte',
// key: 'idEnte',
// },
{
title: 'Tipo',
dataIndex: 'tipo',
key: 'tipo',
sorter: (a:any, b:any) => a.tipo - b.tipo,
},
{
title: 'Stato',
dataIndex: 'stato',
key: 'stato',
filters: stati,
onFilter: (value:any, record:any) => record.stato.indexOf(value) === 0,
sorter: (a:any, b:any) => a.stato - b.stato,
render :(stato: number) => (
<>
{
(stato == 1) ?
(
<Tag color="#00cc00">
Attivo
</Tag>
) :
(
<Tag color="#ff0000">
Non Attivo
</Tag>
)
}
</>)
},
{
title: 'Ultimo aggiornamento password',
dataIndex: 'ultimoAggiornamentoPassword',
key: 'ultimoAggiornamentoPassword',
sorter: (a:any, b:any) => a.ultimoAggiornamentoPassword.localeCompare(b.ultimoAggiornamentoPassword)
},
{
title: '',
dataIndex: 'idUtente',
key: 'idUtente',
render: () => (
//da inserire link per andare al dettaglio / modifica utente
<Button type="primary"><CaretRightOutlined /></Button>
),
},
]
const toolbarControls = React.Children.toArray([
<ExpandButton/>,
<RemoveButton/>
]);
function renderTagColor(text:string): string {
switch(text) {
case 'user':
return 'gold';
case 'administrator':
return 'red';
case 'numeroVerde':
return 'green';
default:
return '';
}
}
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
function handleSearch (selectedKeys:any, confirm:any, dataIndex:any) {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
function handleReset (clearFilters:any) {
clearFilters();
setSearchText('');
};
return (
<MosaicWindow<string>
title="Riepilogo Utenti"
path={path}
toolbarControls={toolbarControls}
>
<Table
dataSource={dataSource}
columns={columns}
bordered={true}
//pagination={{ pageSize:3,position: ['bottomCenter'] }}
pagination={{position: ['bottomCenter'] }}
rowKey={'idUtente'}
//stile per righe striped
rowClassName={(record, index) => index % 2 === 0 ? 'table-row-light' : 'table-row-dark'}
/>
</MosaicWindow>
);
};
export default UserSummaryWindow;
The part which is giving me headeache is (need to convert it to typescript [strict option in config file is enabled]):
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
I'm not here asking you to correct my code , just wanted to know if anyone could share a typescript implementation of that getColumnSearchProps function , or an example of the table component from ant design with typescript code..
Sorry for such a late reply, i was having the same problem.
I found the solution on a github repo someone made. i really wonder why the antd devs don't support typescript in these days.
anyways, here is the repo.
https://github.com/freewind-demos/typescript-react-antd-table-search-column-demo
Basically All the hardwork of moving the old JS code to typescript is done, although there are some linting errors here and there.
I hope anyone who comes looking for this answer really questions their decision to use antd instead of react-table or material-design.
Same answer as Lav Hinsu, but I'll paste the full code here, just in case that link dies.
import "antd/dist/antd.css";
import React from "react";
import ReactDOM from "react-dom";
import { SearchOutlined } from "#ant-design/icons";
import { Table, Button, Input } from "antd";
import { ColumnType } from "antd/lib/table";
function tableColumnTextFilterConfig<T>(): ColumnType<T> {
const searchInputHolder: { current: Input | null } = { current: null };
return {
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
ref={(node) => {
searchInputHolder.current = node;
}}
placeholder="Search"
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() => confirm()}
style={{ width: 188, marginBottom: 8, display: "block" }}
/>
<Button
type="primary"
onClick={() => confirm()}
icon={<SearchOutlined />}
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button size="small" style={{ width: 90 }} onClick={clearFilters}>
Reset
</Button>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined style={{ color: filtered ? "#1890ff" : undefined }} />
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInputHolder.current?.select());
}
},
};
}
type Data = {
key: string;
name: string;
};
const data: Data[] = [
{
key: "1",
name: "John Brown",
},
{
key: "2",
name: "Jim Green",
},
{
key: "3",
name: "Joe Black",
},
];
function Hello() {
return (
<div>
<Table
columns={[
{
title: "Name",
dataIndex: "name",
render: (text: string) => text,
...tableColumnTextFilterConfig<Data>(),
onFilter: (value, record) => {
return record.name
.toString()
.toLowerCase()
.includes(value.toString().toLowerCase());
},
},
]}
dataSource={data}
/>
</div>
);
}
ReactDOM.render(<Hello />, document.body);
In short, you can just extract the tableColumnTextFilterConfig function and implement the onFilter property on the desired column.
This is my code:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import './styles.css';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = React.useState(false);
useEffect(() => {
setChecked(prev => !prev);
}, [])
const [state, setState] = React.useState({
columns: [
{ title: 'name', field: 'name' },
{ title: 'Setor', field: 'sector' }
],
data: [
{ name: 'Tom Brady', setor: 'Quarterback'},
{ name: 'Aaron Rodgers', setor: 'Quarterback'},
{ name: 'Ryan Tannehill', setor: 'Quarterback'},
{ name: 'Julian Edelman', setor: 'Wide Receiver'},
{ name: 'Julio Jones', setor: 'Wide Receiver'},
{ name: 'Marcus Mariota', setor: 'Quarterback'},
{ name: 'Patrick Mahomes', setor: 'Quarterback'},
{ name: 'Antonio Brown', setor: 'Wide Receiver'},
{ name: 'Eli Manning', setor: 'Quarterback'},
{ name: 'Antonio Brown', setor: 'Wide Receiver'},
{ name: 'Mike Evans', setor: 'Wide Receiver'},
{ name: 'Russel Wilson', setor: 'Quarterback'},
{ name: 'Drew Brees', setor: 'Quarterback'},
{ name: 'Cam Newton', setor: 'Quarterback'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (event, rowData) => alert('Edit ' + rowData.name + '?')},
{ icon: 'lock', tooltip: 'Block', onClick: (event, rowData) => alert('Block ' + rowData.name + '?')},
{ icon: 'delete', tooltip: 'Delete', onClick: (event, rowData) => alert('Delete ' + rowData.name + '?')}
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
}
});
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable options={state.options} title="Users" columns={state.columns} data={state.data} actions={state.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
I wanna add this button:
If I follow the default code from here https://material-ui.com/pt/components/tables/, I must add this code:
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
}),
But this code calls a specific function for material-table, I wanna call my own function, how can I add the previous button and call my own function?
My own function will open a modal with some inputs, more inputs than I show on table.
You can create custom actions
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
actions={[
{
icon: "add_box",
tooltip: "my tooltip",
position: "toolbar",
onClick: () => {
console.log("clicked");
}
}
]}
/>
Working demo: https://codesandbox.io/s/material-demo-mgh26
You can override the toolbar, adding your custom button (or some other content!), as explained at https://stackoverflow.com/a/69854673/1288109 :
<MaterialTable
components={{
Toolbar: (props) => (
<div
style={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center"
}}
>
<div style={{ width: "13rem" }}>
<MTableToolbar {...props} />
</div>
<Tooltip title="Add">
<IconButton>
<AddBox />
</IconButton>
</Tooltip>
</div>
),
}}
/>
You can use the position or isFreeAction property for having the action displayed there.
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
actions={[
{
icon: "add_box",
tooltip: "my tooltip",
isFreeAction: true,
onClick: () => {
console.log("clicked");
}
}
]}
/>
The position parameter is more flexible providing different placements for the Button.
isFreeAction in the other hand accepts just a boolean for the action type.
Sandbox cloned from Mosh Feu https://codesandbox.io/s/material-demo-m8eug
How can I copy rows and table content in react ? im using antd library however when i copy the table rows and contents and paste using react-copy -to clipboard currently table data is being store in json this.state.datasource but when i copy i just get [object Object],[object Object] instead of actuall row and column data , can someone pls help thanks!
/*eslint-disable */
import React, { Component } from 'react'
import { Table, Input, Button, Popconfirm, Form } from 'antd'
import { Helmet } from 'react-helmet'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { Menu, Icon } from 'antd';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import styles from './style.module.scss'
const EditableContext = React.createContext();
const EditableRow = ({ form, index, ...props }) => (
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
);
const { SubMenu } = Menu;
const EditableFormRow = Form.create()(EditableRow);
class EditableCell extends React.Component {
state = {
editing: false,
copied: false,
};
onCopy() {
console.log("copied");
alert('copied to clipboard');
}
renderCell = form => {
this.form = form;
const { children, dataIndex, record, title } = this.props;
const { editing } = this.state;
return editing ? (
<Form.Item style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `${title} is required.`,
},
],
);
};
render() {
const {
editable,
dataIndex,
title,
record,
index,
handleSave,
children,
...restProps
} = this.props;
return (
<td {...restProps}>
{editable ? (
<EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>
) : (
children
)}
</td>
);
}
}
class Campaign extends React.Component {
constructor(props) {
super(props);
this.columns = [
{
title: 'id',
dataIndex: 'id',
},
{
title: 'Campaign Name',
dataIndex: 'name',
editable: true,
},
{
title: 'Banners',
dataIndex: 'address',
editable: true,
},
{
title: 'Video',
dataIndex: 'Video',
editable: true,
},
{
title: 'Target',
dataIndex: 'Target',
editable: true,
},
{
title: 'Exchanges',
dataIndex: 'Exchanges',
editable: true,
},
{
title: 'Status',
dataIndex: 'Status',
editable: true,
},
{
title: 'Delete',
dataIndex: 'Banners',
render: (text, record) =>
this.state.dataSource.length >= 1 ? (
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
<a style={{color:'blue'}} href="javascript:;">Delete</a>
</Popconfirm>
) : null,
},
];
this.state = { //table data i want to copy
dataSource: [
{
key: '0',
name: 'Kates Campaign',
id: '32',
Video: 'London, Park Lane no. 0',
Target: 'bacon',
Exchanges: 'Eggs',
},
{
key: '1',
name: 'Fyber Tests',
id: '32',
address: 'Sample Banner Ad (ID 6 ECPM 20.0) ',
},
],
count: 2,
};
}
render() {
const { dataSource } = this.state;
const components = {
body: {
row: EditableFormRow,
cell: EditableCell,
},
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: this.handleSave,
}),
};
});
return (
<div>
<br></br>
<h1> Campaigns </h1>
<br></br><br></br>
<Menu
className={styles.menu}
onClick={this.handleClick}
style={{ marginleft: 80}}
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
mode="inline"
>
<SubMenu className={styles.sub}
key="sub4"
title={
<span>
<Icon type="setting"style={{
color: '#8c54ff'}} />
<span className={styles.title}>Options</span>
</span>
}
>
<span className={styles.icon}> <Icon type="arrow-down" style={{
color: '#8c54ff'}} /></span>
<Menu.Item key="9"> CSV</Menu.Item>
<span className={styles.icon}><Icon type="sync" style={{
color: '#8c54ff'}}/> </span>
<Menu.Item key="11"> Sync</Menu.Item>
<span className={styles.icon}>
<Icon
type="copy"
style={{
color: '#8c54ff',
}}
/>
</span>// this is function to copy table data
<CopyToClipboard text={dataSource} onCopy={() => this.setState({copied: true})}>
</SubMenu>
</Menu>
<br></br>
);
}
}
export default Campaign