How to customize formik handleChange - reactjs
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"
/>
Related
Material UI DataGrid params only passing last value in the table
I have a Data Grid table of users, the last column of the table is meant to be a sub-menu of options that each open up a modal. However, when I try to pass the data of a given row to a modal, it will only pass the final row of the data set (i.e. if I have 10 users, it passes rowId:10). export default function Users() { const [userData, setUserData] = React.useState([]); React.useEffect(() => { let apiClient = new APIClient(); apiClient.getUser().then((response) => { setUserData(response); console.log(response); }); }, []); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [showEditModal, setShowEditModal] = React.useState<boolean>(false); const [showDeleteModal, setShowDeleteModal] = React.useState<boolean>(false); //for each modal const open = Boolean(anchorEl); const moreOptions = (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const columns: GridColDef[] = [ { headerName: 'Name', field: 'name', flex: 1 }, { headerName: 'Email', field: 'email', flex: 1 }, { headerName: 'Date Added', field: 'added', flex: 0.7 }, { headerName: 'Reports To', field: 'reports_to', flex: 1 }, { field: ' ', flex: 0.2, renderCell: (params) => { return ( <> <IconButton id="basic-button" aria-controls={open ? 'basic-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} onClick={moreOptions}> <MoreIcon /> </IconButton><Menu id="basic-menu" anchorEl={anchorEl} open={open} onClose={handleClose} MenuListProps={{ 'aria-labelledby': 'basic-button', }} > <MenuItem> <UserNotification /> </MenuItem> <MenuItem onClick={() => { setShowEditModal(true); handleClose(); } }> <UserEdit /> </MenuItem> <MenuItem onClick={() => { setShowDeleteModal(true); handleClose(); } }> <UserDelete /> </MenuItem> </Menu> <EditUserModal userData={params.row} show={showEditModal} toggleModal={(value) => { setShowEditModal(value); } } /> <DeleteUserModal userData={params.row} show={showDeleteModal} toggleModal={(value) => { setShowDeleteModal(value); } } /> </> ); } }, ]; return ( <div style={{ height: 'auto', width: "100%" }}> <StripedDataGrid rows={userData} columns={columns} getRowClassName={(params) => params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd' } hideFooter={true} autoHeight={true} components={{ Toolbar: GridToolbar }} /> </div> ); } The Data Grid documentation does not have a clear guide on how to pass props of the grid forward. Attempting to populate userData instead of params gives me an overload error. What is the proper way to handle this?
How to use Ant Design DatePicker inside Ant Design Editable Table?
I'm using Ant Design Editable Table where there is an operation column where there is an edit button. I have 2 other columns which are date and value. So what I want to have is when I press on the edit button for a specific row, I want to be able to change the date using <DatePicker /> component from Ant Design. I have not been able to find a solution anywhere online for me. What I have right now is: EditableTable.js import React, { useState } from "react"; import { Table, Form, Button, DatePicker } from "antd"; import EditableCell from "./EditableCell"; const OtherLocsTable = ({ data, setData }) => { const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(""); const isEditing = (record) => record.key === editingKey; const edit = (record) => { form.setFieldsValue({ date: "", measurement: "", ...record, }); setEditingKey(record.key); }; const save = async (key) => { try { const row = await form.validateFields(); const newData = [...data]; const index = newData.findIndex((item) => key === item.key); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, ...row }); setData(newData); setEditingKey(""); } else { newData.push(row); setData(newData); setEditingKey(""); } } catch (errInfo) { console.log("Validate Failed:", errInfo); } }; const columns = [ { title: "Date", dataIndex: "date", key: "date", editable: true, }, { title: "Measurement", dataIndex: "measurement", key: "measurement", editable: true, }, { title: "Operation", dataIndex: "operation", render: (_, record) => { const editable = isEditing(record); const lastElement = [...data].pop(); return editable ? ( <span> <a href="javascript:;" onClick={() => save(record.key)} style={{ marginRight: 8, }} > Save </a> <Popconfirm title="Sure to cancel?" onConfirm={cancel}> <a>Cancel</a> </Popconfirm> </span> ) : ( <> <Typography.Link disabled={editingKey !== ""} onClick={() => edit(record)} > Edit </Typography.Link> {data.length > 1 ? ( record.key === lastElement.key ? ( <Typography.Link type="danger" disabled={editingKey !== ""} onClick={() => deleteRow(record)} style={{ marginLeft: 15, }} > Delete </Typography.Link> ) : null ) : null} </> ); }, }, ]; const mergedColumns = columns.map((col) => { if (!col.editable) { return col; } return { ...col, onCell: (record) => ({ record, inputType: col.dataIndex === loc ? "number" : col.dataIndex === "date" ? "date" : "text", dataIndex: col.dataIndex, title: col.title, editing: isEditing(record), }), }; }); return ( <> <Popconfirm title="Confirm save changes?" onConfirm={handleSubmit}> <Button key="submit" style={{ marginBottom: "1em", width: "100%", backgroundColor: "green", }} > Save Changes </Button> </Popconfirm> <Form form={form} component={false}> <Table components={{ body: { cell: EditableCell, }, }} bordered dataSource={data} columns={mergedColumns} rowClassName="editable-row" pagination={{ onChange: cancel, }} rowKey="date" /> </Form> </> ); }; } EditableCell.j import React from "react"; import { Form, Input, InputNumber, DatePicker } from "antd"; import moment from "moment"; const EditableCell = ({ editing, dataIndex, title, inputType, record, index, children, ...restProps }) => { const inputNode = inputType === "number" ? ( <Form.Item style={{ margin: 0 }} name={dataIndex} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} > <InputNumber formatter={(value) => value} parser={(value) => value} /> </Form.Item> ) : inputType === "date" ? ( <FormItem style={{ margin: 0 }} name={dataIndex} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} initialValue={moment(record[dataIndex])} > <DatePicker /> </FormItem> ) : ( <Form.Item style={{ margin: 0 }} name={dataIndex} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} > <Input /> </Form.Item> ); return <td {...restProps}>{editing ? inputNode : children}</td>; }; export default EditableCell; UPDATE Currently with the code I have, I'm getting an error where it says date.clone is not a function
I found a solution EditableTable.js import React, { useState } from "react"; import { Table, Form, Button, DatePicker } from "antd"; import moment from 'moment'; import EditableCell from "./EditableCell"; const OtherLocsTable = ({ data, setData }) => { const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(""); const isEditing = (record) => record.key === editingKey; const edit = (record) => { const {date, measurement} = record; form.setFieldsValue({ date: (date) ? moment(date) : "", measurement: measurement || "" }); setEditingKey(record.key); }; const save = async (key) => { try { const row = await form.validateFields(); const newData = [...data]; const index = newData.findIndex((item) => key === item.key); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, ...row }); setData(newData); setEditingKey(""); } else { newData.push(row); setData(newData); setEditingKey(""); } } catch (errInfo) { console.log("Validate Failed:", errInfo); } }; const columns = [ { title: "Date", dataIndex: "date", key: "date", editable: true, }, { title: "Measurement", dataIndex: "measurement", key: "measurement", editable: true, }, { title: "Operation", dataIndex: "operation", render: (_, record) => { const editable = isEditing(record); const lastElement = [...data].pop(); return editable ? ( <span> <a href="javascript:;" onClick={() => save(record.key)} style={{ marginRight: 8, }} > Save </a> <Popconfirm title="Sure to cancel?" onConfirm={cancel}> <a>Cancel</a> </Popconfirm> </span> ) : ( <> <Typography.Link disabled={editingKey !== ""} onClick={() => edit(record)} > Edit </Typography.Link> {data.length > 1 ? ( record.key === lastElement.key ? ( <Typography.Link type="danger" disabled={editingKey !== ""} onClick={() => deleteRow(record)} style={{ marginLeft: 15, }} > Delete </Typography.Link> ) : null ) : null} </> ); }, }, ]; const mergedColumns = columns.map((col) => { if (!col.editable) { return col; } return { ...col, onCell: (record) => ({ record, inputType: col.dataIndex === loc ? "number" : col.dataIndex === "date" ? "date" : "text", dataIndex: col.dataIndex, title: col.title, editing: isEditing(record), }), }; }); return ( <> <Popconfirm title="Confirm save changes?" onConfirm={handleSubmit}> <Button key="submit" style={{ marginBottom: "1em", width: "100%", backgroundColor: "green", }} > Save Changes </Button> </Popconfirm> <Form form={form} component={false}> <Table components={{ body: { cell: EditableCell, }, }} bordered dataSource={data} columns={mergedColumns} rowClassName="editable-row" pagination={{ onChange: cancel, }} rowKey="date" /> </Form> </> ); }; } EditableCell.j import React from "react"; import { Form, Input, InputNumber, DatePicker } from "antd"; import moment from "moment"; const EditableCell = ({ editing, dataIndex, title, inputType, record, index, children, ...restProps }) => { const inputNode = inputType === "number" ? ( <Form.Item style={{ margin: 0 }} name={dataIndex} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} > <InputNumber formatter={(value) => value} parser={(value) => value} /> </Form.Item> ) : inputType === "date" ? ( <FormItem style={{ margin: 0 }} name={dataIndex} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} > <DatePicker formate="DD-MM-YYYY" /> </FormItem> ) : ( <Form.Item style={{ margin: 0 }} name={dataIndex} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} > <Input /> </Form.Item> ); return <td {...restProps}>{editing ? inputNode : children}</td>; }; export default EditableCell;
How to delete multiple selected rows in Material-UI DataGrid?
I wanna know how to delete rows from the DataGrid from Material-UI by using the checkboxes in React. I haven't find any proper tutorial for doing this on DataGrid although I found one for MaterialTable but is not the same. Any help is welcome. UPDATE My full code after adapt solution: import React, { useState, useEffect, Fragment } from 'react' import {db} from './firebase'; import { useHistory, useLocation } from 'react-router-dom'; import "./ListadoEstudiantes.css" import * as locales from '#mui/material/locale'; import { DataGrid, GridRowsProp, GridColDef, GridToolbarContainer, GridToolbarColumnsButton, GridToolbarFilterButton, GridToolbarExport, GridToolbarDensitySelector} from '#mui/x-data-grid'; import { Button, Container } from "#material-ui/core"; import { IconButton} from '#mui/material'; import PersonAddIcon from '#mui/icons-material/PersonAddSharp'; import DeleteOutlinedIcon from '#mui/icons-material/DeleteOutlined'; import { Box } from '#mui/system'; function ListadoEstudiantes({user}) { const history = useHistory(""); const crearEstudiante = () => { history.push("/Crear_Estudiante"); }; const [estudiantesData, setEstudiantesData] = useState([]) const parseData = { pathname: '/Crear_Pedidos', data: estudiantesData } const realizarPedidos = () => { if(estudiantesData == 0) { window.alert("Seleccione al menos un estudiante") } else { history.push(estudiantesData); } }; function CustomToolbar() { return ( <GridToolbarContainer> <GridToolbarFilterButton /> <GridToolbarDensitySelector /> </GridToolbarContainer> ); } const [estudiantes, setEstudiantes] = useState([]); const [selectionModel, setSelectionModel] = useState([]); const columns = [ { field: 'id', headerName: 'ID', width: 100 }, {field: 'nombre', headerName: 'Nombre', width: 200}, {field: 'colegio', headerName: 'Colegio', width: 250}, {field: 'grado', headerName: 'Grado', width: 150}, { field: "delete", width: 75, sortable: false, disableColumnMenu: true, renderHeader: () => { return ( <IconButton onClick={() => { const selectedIDs = new Set(selectionModel); setEstudiantes((r) => r.filter((x) => !selectedIDs.has(x.id))); }} > <DeleteOutlinedIcon /> </IconButton> ); } } ]; const estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes") useEffect(() => { estudiantesRef.onSnapshot(snapshot => { const tempData = []; snapshot.forEach((doc) => { const data = doc.data(); tempData.push(data); }); setEstudiantes(tempData); }) }, []); return ( <Container fixed> <Box mb={5} pt={2} sx={{textAlign:'center'}}> <Button startIcon = {<PersonAddIcon />} variant = "contained" color = "primary" size = "medium" onClick={crearEstudiante} > Crear Estudiantes </Button> <Box pl={25} pt={2} sx={{height: '390px', width: "850px", textAlign:'center'}}> <DataGrid rows={estudiantes} columns={columns} pageSize={5} rowsPerPageOptions={[5]} components={{ Toolbar: CustomToolbar, }} checkboxSelection //Store Data from the row in another variable onSelectionModelChange = {(id) => { setSelectionModel(id); const selectedIDs = new Set(id); const selectedRowData = estudiantes.filter((row) => selectedIDs.has(row.id) ); setEstudiantesData(selectedRowData) console.log(estudiantesData); } } {...estudiantes} /> </Box></Box></Container> ) } export default ListadoEstudiantes UPDATE Everything works! thank you
You can keep track of the currently selected IDs via selectionModel/onSelectionModelChange props, and perform the necessary action when the user click the IconButton on the header. Because renderHeader callback doesn't provide the selection state, I have to make use of closure by putting the columns definition inside the function body so I can reference selectionModel in the callback: const [rows, setRows] = React.useState(_rows); const [selectionModel, setSelectionModel] = React.useState([]); const columns: GridColDef[] = [ { field: "col1", headerName: "Column 1", width: 150 }, { field: "col2", headerName: "Column 2", width: 150 }, { field: "delete", width: 75, sortable: false, disableColumnMenu: true, renderHeader: () => { return ( <IconButton onClick={() => { const selectedIDs = new Set(selectionModel); // you can call an API to delete the selected IDs // and get the latest results after the deletion // then call setRows() to update the data locally here setRows((r) => r.filter((x) => !selectedIDs.has(x.id))); }} > <DeleteIcon /> </IconButton> ); } } ]; return ( <div style={{ height: 400, width: "100%" }}> <DataGrid rows={rows} columns={columns} checkboxSelection onSelectionModelChange={(ids) => { setSelectionModel(ids); }} /> </div> );
"react-useanimations" Property 'animationKey' does not exist on type 'IntrinsicAttribute
I have used "react-useanimations" plugin, when i run my project showing me the following error. Property 'animationKey' does not exist on type 'IntrinsicAttribute common.js-> common functions are written here (simple js file) myjob.tsx-> actual getTag function used on this page (typescript page) // Common.js file import React from "react"; import UseAnimations from "react-useanimations"; export function getTag(tag: any) { if (tag === 'DL') return ( <UseAnimations animationKey="github" size={56} style={{ padding: 100 }} /> ); } // myjob.tsx import React, { useState, useEffect } from "react"; import SVG from "react-inlinesvg"; import { Link } from "react-router-dom"; import { Alert, Nav, Tab } from "react-bootstrap"; import { toAbsoluteUrl } from "../../_metronic/_helpers"; import { utcToDate, getTag } from "../../utils/common"; import Icon from '#material-ui/core/Icon'; import { MDBDataTableV5 } from 'mdbreact'; import { Dropdown, Button } from "react-bootstrap"; import { DropdownCustomToggler } from "../../_metronic/_partials/dropdowns"; import Paper from '#material-ui/core/Paper'; import Draggable from 'react-draggable'; import { getUserJobList, deleteJobById } from "../../app/modules/Job/_redux/jobCrud"; import { shallowEqual, useSelector } from "react-redux"; export function MyJobs(props: any) { const [open, setOpen] = React.useState(false); const [openSnackbar, setOpenSnackbar] = React.useState(false); const [deleteJobId, setDeleteJobId] = useState(""); const [key, setKey] = useState("Month"); const [msg, setMsg] = useState("") const [type, setType] = useState<"success" | "primary" | "secondary" | "danger" | "warning" | "info" | "dark" | "light" | undefined>("success") const [jpbList, setJpbList] = useState([]) const [displayBy, setDisplayBy] = useState(false) const [folders, setFolders] = useState() const user = useSelector((state: any) => state.auth.user, shallowEqual); const [datatable, setDatatable] = useState({ columns: [ { label: '#', field: 'icon', width: 150, attributes: { 'aria-controls': 'DataTable', 'aria-label': 'Name', }, }, { label: 'Job Name', field: 'name', width: 150, attributes: { 'aria-controls': 'DataTable', 'aria-label': 'Name', }, }, { label: 'Proccesed Date', field: 'createdDttm', width: 270, }, { label: 'Status', field: 'status', width: 270, }, { label: 'Action', field: 'action', width: 270, }, ], rows: [{}], }); useEffect(() => { if (!jpbList.length) { getList(); } }, []); const getList = async () => { getUserJobList(user.id) .then((res: any) => { if (res.status == 200 && res.data) { let rows: any = []; res.data.map((row: any) => { rows.push( { icon:<img src={row.thumbnail} style={{ maxWidth: '50px', maxHeight: '50px' }} />, name: row.name, createdDttm: utcToDate(row.createdDttm), status: getTag(row.status), action: <div style={{ width: '120px' }}> {/* begin::Toolbar */} <div className="d-flex justify-content-end"> { row.status == "CO" ? <Link to={`myjobs/markerpage/${row.id}`} className="btn btn-icon btn-sm mx-3"> <span className="svg-icon svg-icon-md svg-icon-primary"> <Icon className='fa fa-play' color="action" /> </span> </Link> : "" } < Dropdown className="dropdown dropdown-inline" alignRight> <Dropdown.Toggle id="dropdown-toggle-top-user-profile" as={DropdownCustomToggler} > <i className="ki ki-bold-more-hor"></i> </Dropdown.Toggle> <Dropdown.Menu className="dropdown-menu dropdown-menu-sm dropdown-menu-right"> </Dropdown.Menu> </Dropdown> </div> {/* end::Toolbar */}</div >, } ) }); let dt: any = []; dt.columns = datatable.columns; dt.rows = rows; setDatatable(dt); setJpbList(res.data); } else { setMsg("Something went wrong please try again later.") setType("danger") } }) .catch((e: any) => { setMsg("Something went wrong please try again later.") setType("danger") }); } return ( <> <MDBDataTableV5 hover entriesOptions={[10, 20, 50]} entries={10} pagesAmount={4} data={datatable} searchTop searchBottom={false} /> </>); }
First import icon which you want. import UseAnimations from "react-useanimations"; import activity from 'react-useanimations/lib/activity' In view or in render use like this <UseAnimations animation={activity} autoplay={true} loop={true} size={20} style={{ paddingLeft: 10 }} />
And Design Table TypeScript getColumnSearchProps example
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.