I am trying to to make a MUI data table to be collapsible but I can't make it. So I have working demo of collapsible MUI table here https://codesandbox.io/s/19r60?file=/src/ExpandableRowTable.js
How can I transform the existing non collapsible MUI data table to collapsible MUI table?
Here is my code
TripsTable.js (minimal code)
function TripsTable({ data, userSettings, refetchData, ...props }) {
const classes = useStyles();
const { t } = useTranslation();
const isKm = userSettings.metrics === Metrics.KM;
const columns = [
{
field: "collision",
headerName: " ",
flex: 0.7,
disableColumnMenu: true,
renderCell: (params) => (
<span className={classes.iconsCell}>
{params.row.events
.map((e) => e.event_type_name)
.includes("COLLISION") && (
<img src={collision} alt="" title="Collision" />
)}
{params.row.isBeaconTagged && (
<img src={BeaconsIcon} alt="" title="Beacon" />
)}
{params.row.isBluetoothTagged && (
<img src={BluetoothIcon} alt="" title="Bluetooth" />
)}
</span>
),
},
{
field: "tripStartTimeStr",
headerName: t("date"),
flex: 1.5,
disableColumnMenu: true,
sortComparator: (v1, v2, p1, p2) => new Date(v1) - new Date(v2),
renderCell: (params) => (
<pre>
<span
style={{
marginLeft: 0,
padding: "5px 7px",
borderRadius: 7,
color: params.row.score < 72 ? "#fff" : "",
fontWeight: "normal",
backgroundColor:
params.row.score < 72 ? "#F44138" : "transparent",
}}
>
{params.value}
</span>
</pre>
),
},
{
field: "tripType",
headerName: t("tripType"),
flex: 1.4,
disableColumnMenu: true,
hide: isHiddenTripType,
renderCell: (params) => (
<TripTypeSelector
type={params.value}
tripId={params.row.tripId}
refetchTrips={refetchData}
/>
),
},
{
field: "drivingTimeSeconds",
headerName: t("drivingTime"),
flex: 1.1,
disableColumnMenu: true,
sortComparator: (v1, v2, p1, p2) =>
p1.api.getRow(p1.id).drivingTimeSeconds -
p2.api.getRow(p2.id).drivingTimeSeconds,
renderCell: (params) => (
<span>
{Math.round(params.value / 60)} {t("minutes")}
</span>
),
},
{
field: "distanceDriven",
headerName: t(isKm ? "kilometersDriven" : "milesDriven"),
flex: 1.1,
disableColumnMenu: true,
sortComparator: (v1, v2, p1, p2) =>
p1.api.getRow(p1.id).distanceDriven -
p2.api.getRow(p2.id).distanceDriven,
valueFormatter: (params) =>
`${params.value.toFixed(2)} ${t(isKm ? "km" : "mi")}`,
},
...DrivingBehaviourKeysArr.map((evnt) => ({
field: evnt,
headerName: t(evnt),
flex: 0.9,
disableColumnMenu: true,
renderCell: eventScoreCell,
})),
{
field: "fleetId",
headerName: t("Fleet"),
flex: 1.1,
disableColumnMenu: true,
},
{
field: "category",
headerName: t("category"),
flex: 1.1,
disableColumnMenu: true,
sortComparator: (v1, v2, param1, param2) =>
param1.api.getCellValue(param1.id, "score") -
param2.api.getCellValue(param2.id, "score"),
renderCell: (params) => (
<span style={{ color: getCategoryColor(params.row.score) }}>
{params.value}
</span>
),
},
{
field: "score",
headerName: t("score"),
flex: 1,
disableColumnMenu: true,
},
];
const [isVisible, setIsVisible] = useState(true);
const { fleetId, driverId } = useParams();
const { tripId } = useQuery();
const history = useHistory();
const ref = useRef();
const filteredData = isOnlyBusinessTrips
? data.filter((row) => row.tripType == TripTypes.BUSINESS)
: data;
const trip = useMemo(
() => filteredData.find((t) => t.tripId === tripId),
[filteredData, tripId]
);
useEffect(() => {
setTimeout(() => ref.current?.scrollIntoView({ behavior: "smooth" }), 100);
}, [isVisible, trip]);
const handleSelectTrip = useCallback(
(row) => {
if (row.id !== tripId) {
history.push(FrontendRoutes.DRIVER_OVERVIEW(fleetId, driverId, row.id));
setIsVisible(true);
} else {
setIsVisible(!isVisible);
}
},
[isVisible, driverId, tripId]
);
const rows = useMemo(
() => filteredData.map((d) => ({ ...d, id: d.tripId })),
[filteredData]
);
// console.log("data", data);
return (
<ChartWrapper {...props}>
<div className={classes.root}>
<DataTable
selectionModel={tripId ? [tripId] : undefined}
rows={rows}
columns={columns}
onCellClick={handleSelectTrip}
/>
</div>
{trip && isVisible && (
<DriverTripOverview trip={trip} userSettings={userSettings} />
)}
<span ref={ref} />
</ChartWrapper>
);
}
export default memo(TripsTable);
How can show DriverTripOverview component below every clicked table row?
Related
Basically I have table when I click the row it opens the EditProperties dialog. The dialog has fields where you can add and delete fields.
The fields are dynamic so I dont need to repeat creating html it is based on the RegionalListData data.
Now each field the (EmailItem) on the dialog is an auto complete field where you can type and will pass values to the api and will return the result based on the api result.
The issue right now is when I type values on the input field it does not display my input and sometimes it displays , also when I type to the input field the value are sometimes directly erased and I dont know why.
Also when I select one item on the result it does not display on the input field.
My goal is to be able to type and search and it will call the api , it will update the call when I update my search input.
For example when I type Mark it should return all the results that matches mark. I should be able to search without it freezing.
Thanks for any help or idea , would be much appreacited.
#sample research object from the api
{
"isSuccess":true,
"message":"",
"data":[
{
"id":151,
"emailAddress":"mark.jasen#cushwar.com",
"firstName":"Mark",
"lastName":"Jasen"
},
{
"id":808,
"emailAddress":"mark.turmor#stream.com",
"firstName":"Mark",
"lastName":"Turmor"
},
{
"id":1228,
"emailAddress":"mark.logan#biias.com",
"firstName":"Mark",
"lastName":"Logan"
},
]
}
#Code snippet - this calls and opens the dialog
<EditProperties open={open} handleClose={handleClose} selectedRow={selectedRow} />
#EditProperties ts code
export const RegionalListData: IRegionalList[] = [
{
id: 4,
name: "Associate Director of Construction Ops",
column: "associateDirectorofConstructionOps",
emails: [
{
emailAddress: "associateDir#gmail.com",
firstName: "Associate",
lastName: "Director",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
},
{
id: 5,
name: "CAM Manager",
column: "camManager",
emails: [
{
emailAddress: "associateDir#gmail.com",
firstName: "Associate",
lastName: "Director",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
},
{
id: 6,
name: "CAO-Chief Administrative Officer",
column: "caoChiefAdministrativeOfficer",
emails: [
{
emailAddress: "associateDir#gmail.com",
firstName: "Associate",
lastName: "Director",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
},
];
type InitialReqPaylod = {
accountId: number;
regionalRoleUserDto: IRegional;
};
type IData = {
regionName: string;
marketName: string;
subRegionName: string;
};
type IEmail = {
emailAddress: string;
firstName: string;
id: number;
lastName: string;
};
const EditProperties: FC<EditPropertiesProps> = ({
open,
handleClose,
selectedRow,
}) => {
const dispatch = useAppDispatch();
const [isEmailOpen, setOpenEmail] = useState(false);
const [fetching, setFetching] = useState(false);
const [RegionalList, setRegionalList] = useState<IRegionalList[]>(
RegionalListData
);
const [data, setData] = useState<IData>({
regionName: "",
marketName: "",
subRegionName: "",
});
const [regionalId, setRegionalId] = useState<number | null>(null);
const [emailCurrentIndex, setEmailCurrentIndex] = useState<number | null>(
null
);
const [selectedEmailId, setSelectedEmailId] = useState<number | null>(null);
const { isSuccess } = useAppSelector((state) => state.yardUser);
const { isSaveSuccess } = useAppSelector((state) => state.region);
const email = useAppSelector((state) => state.yardUser);
const [emailOptions, setEmailOptions] = useState<IEmail[]>([]);
const emailList = email.data ? email.data.data : [];
useEffect(() => {
if (selectedRow) {
setData({
regionName: selectedRow["regionName"],
marketName: selectedRow["marketName"],
subRegionName: selectedRow["subRegionName"],
});
let regional = [...RegionalList];
for (const k in selectedRow) {
regional.map((prop: IRegionalList) => {
if (prop.column === k) {
prop.emails = selectedRow[k] ? selectedRow[k] : [];
}
});
}
setRegionalList(regional);
}
}, [selectedRow]);
const [maxWidth, setMaxWidth] = React.useState<DialogProps["maxWidth"]>("md");
const fetchEmailResult = React.useMemo(
() =>
throttle(
(event: any, callback: (results: IEmail[]) => void) => {
const payload: IYardUserRequestPayload | InitialReqPaylod = {
accountId: 1,
searchString: event.target.value,
};
fetch(
`https://jsonplaceholder.typicode.com/users?email=${event.target.value}`
)
.then((res) => res.json())
.then((res) => res.data ? callback(res.data.slice(0, 10)) : callback([]))
},
200
),
[]
);
const emailOnChange = (event: any, regionalId: number, index: number, emailId: number) => {
setRegionalId(regionalId);
setEmailCurrentIndex(index);
setSelectedEmailId(emailId);
fetchEmailResult(event,(results: IEmail[]) => {
console.log('results' , results)
if (results.length) setEmailOptions(results);
});
};
useEffect(() => {
if (isSaveSuccess) {
handleClose();
}
}, [isSaveSuccess]);
useEffect(() => {
if (isSuccess) {
setFetching(false);
}
}, [isSuccess]);
const addEmail = (id: number) => {
setRegionalList((list) =>
list.map((item) => {
if (item.id === id) {
return {
...item,
emails: [
...item.emails,
{
emailAddress: "",
firstName: "",
lastName: "",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
};
}
return item;
})
);
};
const deleteEmail = (email: IEmail, regionId: number) => {
const regionalListCopy = [...RegionalList].map((prop: IRegionalList) => {
if (prop.id === regionId) {
return {
...prop,
emails: prop.emails.filter((prop) => prop.id !== email.id),
};
}
return { ...prop };
});
setRegionalList(regionalListCopy);
};
const setOnChangeOption = (email) => {
setSelectedEmailId(null);
setRegionalList((list) =>
list.map((item) => {
if (item.id === regionalId) {
return {
...item,
emails: [
...item.emails.map((prop) => {
return {
...prop,
...email,
};
}),
],
};
}
return item;
})
);
};
const EmailItem = ({ email, mIndex, prop }) => (
<>
<div style={{ display: "block" }} key={email.id}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: 15
}}
>
<Autocomplete
options={emailOptions}
getOptionLabel={(option: IEmail) => option.emailAddress}
onInputChange={($event) => emailOnChange($event, prop.id, mIndex, email.id)}
onChange={($event, value) => setOnChangeOption(value)}
fullWidth
open={email.id === selectedEmailId}
renderInput={(params) => (
<TextField size="small" {...params} variant="standard" />
)}
renderOption={(props, option) => {
return (
<Box component="li" {...props}>
{option.emailAddress}
</Box>
);
}}
/>
<DeleteIcon
style={{ color: "red", cursor: "pointer" }}
onClick={() => deleteEmail(email, prop.id)}
/>
</div>
<div
style={{
fontSize: ".8em",
display: "flex",
justifyContent: "space-between",
}}
>
<span style={{ paddingTop: 5 }}>
Email : {email.emailAddress}
Full Name: {email.firstName} {email.lastName}
</span>
{/* <span style={{ paddingRight : 40 }}>{fetching ? "Fetching...." : null}</span> */}
</div>
</div>
</>
);
return (
<Dialog
maxWidth={maxWidth}
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Edit</DialogTitle>
<DialogContent>
<Card sx={{ minWidth: 275 }} style={{ padding: 20 }}>
<div>
<span>Sub-Region (Sub-Division)</span>
<Divider style={{ marginTop: 10 }} />
<FormControl sx={{ mt: 2, minWidth: 720 }}>
<TextField
label="Region (Division)"
variant="filled"
value={data.regionName}
/>
</FormControl>
</div>
<div style={{ marginTop: 10 }}>
<span>Sub-Region (Sub-Division)</span>
<Divider style={{ marginTop: 10 }} />
<FormControl sx={{ mt: 2, minWidth: 720 }}>
<TextField
label="Sub-Region (Sub-Division)"
variant="filled"
value={data.subRegionName}
/>
</FormControl>
</div>
<div style={{ marginTop: 10 }}>
<span>Market</span>
<Divider style={{ marginTop: 10 }} />
<FormControl sx={{ mt: 2, minWidth: 720 }}>
<TextField
label="Market"
variant="filled"
value={data.marketName}
/>
</FormControl>
</div>
</Card>
{RegionalList.map((prop: IRegionalList, index: number) => (
<Card
sx={{ minWidth: 275 }}
style={{ overflow: "visible", padding: 20, marginTop: 20 }}
key={prop.id}
>
<div style={{ display: "flex", alignItems: "center" }}>
{prop.name}*{" "}
<AddIcon
style={{ marginLeft: 5, cursor: "pointer" }}
onClick={() => addEmail(prop.id)}
/>
</div>
<Divider style={{ marginTop: 10 }} />
{prop.emails.map((email: IEmail, mIndex: number) => (
<EmailItem
key={email.id}
prop={prop}
email={email}
mIndex={mIndex}
/>
))}
</Card>
))}
</DialogContent>
<DialogActions
style={{ marginTop: "20px", marginRight: "20px", marginBottom: "20px" }}
>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="contained" onClick={() => saveChanges()} autoFocus>
Save Changes
</Button>
</DialogActions>
</Dialog>
);
};
export default EditProperties;
I am getting error in my react App. I am using Material UI .
I get this error when I visit this page.
As per the error I am getting error in TablePagination, but I am not even using TablePagination component.
I am using Data Grid component of Material UI version 4. It has server-side pagination enabled and I am passsing in the number of rows as row count.(Below is the code.)
MiList component is using Data Grid.
import React, { useState, useEffect, useRef, memo } from "react";
import { MiButton, MiLink, MiList } from "../../../components/Standard/MiView";
import CreateNewPropertyModal from "./CreateNewPropertyModal";
import { THEME } from "../../../components/Standard/theme";
import { Chip } from "#miview/components";
import "./StyleTable.css";
import { debounce } from "../../../utils";
import { homeSearchHook } from "./utils";
import { formatDate } from "../../../helpers/dateTools";
import {
systemTypeService,
stageTypeService,
propertyService,
} from "#miview/api";
import { SYSTEM_TYPE_CATEGORIES } from "#miview/constants";
import { useComponentState } from "#miview/hooks";
import { MiTHEME } from "#miview/theme";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(() => ({
tableStyles: {
border: 0,
"& .MuiDataGrid-columnHeaderTitleContainer": {
padding: 0,
},
"& .MuiDataGrid-columnSeparator": {
visibility: "hidden",
},
},
}));
const Properties = memo((props) => {
const [newPropertyInProgress, setNewPropertyInProgress] = useState();
const [newOutlineInProgress, setNewOutlineInProgess] = useState();
const [stageTypes, setStageTypes] = useState([]);
const [garageSwingTypes, setGarageSwingTypes] = useState([]);
const [changed, setChanged] = useState(false);
const [sorted, setSorted] = useState([]);
const [addressInfo, setAddressInfo] = useState(null);
const [properties, setproperties] = useState([]);
const [pageNo, setpageNo] = useState(0);
const [pageSize, setpageSize] = useState(50);
const [rowCount, setrowCount] = useState(0);
const [searchTerm, setsearchTerm] = useState("");
// const [setupStatuses, setSetupStatuses] = useState([]);
const [homesMessage, getHomes] = homeSearchHook();
const [hasUserSearched, setHasUserSearched] = useState(false);
const debouncedSearchTerm = debounce(searchTerm, 300);
const isFirstRun = useRef(true);
const classes = useStyles();
const getColor = (val) => {
switch (val) {
case "Stage Complete":
return MiTHEME.colors.green.light;
case "Scheduled":
return MiTHEME.colors.blue.light;
case "Not Scheduled":
return MiTHEME.colors.bluegrey.light;
case "Not Ready":
return MiTHEME.colors.bluegrey.light;
default:
return MiTHEME.colors.blue.light;
}
};
const getTextColor = (val) => {
switch (val) {
case "Stage Complete":
return MiTHEME.colors.green.primary;
case "Scheduled":
return MiTHEME.colors.blue.primary;
case "Not Scheduled":
return MiTHEME.colors.bluegrey.primary;
case "Not Ready":
return MiTHEME.colors.bluegrey.primary;
default:
return MiTHEME.colors.blue.primary;
}
};
const stateManager = useComponentState();
useEffect(() => {
handleSearchHomes();
isFirstRun.current = false;
}, [debouncedSearchTerm]);
const handleSearchHomes = () => {
setHasUserSearched(!isFirstRun.current);
setpageNo(0);
filterData({
page: 0,
searchTerm: debouncedSearchTerm,
});
};
useEffect(() => {
// getSetupStatuses();
getStageTypes();
getGarageSwingTypes();
}, []);
// const getSetupStatuses = async () => {
// stateManager.run(async () => {
// const response = await propertyService.getSetupStatuses();
// setSetupStatuses(response);
// });
// };
const getStageTypes = async () => {
stateManager.run(async () => {
stageTypeService.getAll({}).then((response) => {
setStageTypes(response.filter((r) => r.stageOrder !== -1));
});
});
};
const getGarageSwingTypes = async () => {
stateManager.run(async () => {
const response = await systemTypeService.getSystemTypesByName({
name: SYSTEM_TYPE_CATEGORIES.GARAGE_SWING,
});
const mapped = response.map((i, k) => {
return { value: i.systemTypeId, key: k, text: i.mainValue };
});
setGarageSwingTypes(mapped);
});
};
useEffect(() => {
if (!changed && stageTypes.length > 0) {
let stageTypesToChange = stageTypes;
stageTypesToChange.push({ stageTypeName: "No Stage" });
setStageTypes(stageTypesToChange);
setChanged(true);
}
}, [stageTypes]);
// const getPropertySetupStatusFromCustomFields = (customFieldsJson) => {
// if (!customFieldsJson) return false;
// const fields = JSON.parse(customFieldsJson);
// const propertySetupStatuses = fields.find(
// (f) => f.name === "PropertySetupStatus"
// )?.value;
// const statusReducer = (a, s) =>
// a &&
// propertySetupStatuses.filter((ps) => ps.id == s.statusId && ps.value)
// .length > 0;
// return setupStatuses.reduce(statusReducer, true);
// };
const filterData = (params = {}) => {
stateManager.run(async () => {
getHomes(
params.page !== undefined ? params.page : pageNo,
params.pageSize || pageSize,
params.sorted || sorted,
params.searchTerm || searchTerm,
isFirstRun.current,
setpageNo,
setpageSize,
setrowCount,
setproperties
);
});
};
const toggleModal = () => {
setNewPropertyInProgress(!newPropertyInProgress);
};
const headerContent = (
<MiButton
title="Home"
icon="add"
inverse={true}
onClick={toggleModal}
color={THEME.GREEN_PRIMARY}
/>
);
const renderHeader = ({ colDef }) => {
return (
<div
style={{
color: MiTHEME.colors.blue.primary,
fontWeight: MiTHEME.fontWeight.bold,
}}
>
{colDef.headerName}
</div>
);
};
const columns = [
{
field: "addressLine1",
headerName: "Address",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 1,
renderCell: (p) => {
return (
<MiLink
to={"/homes/" + p.propertyId}
title={p.value}
style={{ marginLeft: -8 }}
/>
);
},
},
{
field: "cityName",
headerName: "City",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 0.7,
},
{
field: "state",
headerName: "State",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 0.6,
},
{
field: "community",
headerName: "Community",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 1,
},
{
field: "builder",
headerName: "Builder",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 1,
},
{
field: "currentStage",
headerName: "Stage",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 0.8,
renderCell: (p) => {
return (
<MiLink
to={"/stages/" + p.currentStageId}
title={p.value}
style={{ marginLeft: -8 }}
/>
);
},
},
{
field: "currentStageScheduledDate",
headerName: "Schedule Date",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 1,
valueFormatter: (params) => {
return formatDate(params.value);
},
},
{
field: "propertyStatus",
headerName: "Status",
width: 150,
renderHeader: renderHeader,
headerAlign: "left",
align: "left",
flex: 1,
renderCell: ({ value }) => {
return (
<Chip
text={value}
style={{
display: "flex",
borderRadius: "30px",
height: "30px",
minWidth: "80px",
justifyContent: "center",
padding:'0 7px 0 7px'
}}
color={getColor(value)}
textColor={getTextColor(value)}
/>
);
},
},
];
const toggleOutliner = () => {
setNewOutlineInProgess(!newOutlineInProgress);
};
useEffect(() => {
if (addressInfo) {
toggleOutliner();
}
}, [addressInfo]);
const modals = [
newPropertyInProgress ? (
<CreateNewPropertyModal
key={0}
getAddressInfo={setAddressInfo}
toggle={toggleModal}
stageTypes={stageTypes}
garageSwingTypes={garageSwingTypes}
history={props.history}
></CreateNewPropertyModal>
) : (
""
),
];
return (
<>
{stateManager.statusTag("propertyListStatus")}
<MiList
data={properties}
headerTitle={`Homes - ${hasUserSearched ? "All" : "Recent"}`}
headerIcon={"home"}
className={classes.tableStyles}
getRowId={(row) => row.propertyId}
headerContent={headerContent}
modals={modals}
callouts={null}
columns={columns}
manual
defaultPageSize={50}
pages={rowCount}
fetchData={isFirstRun.current ? () => null : filterData}
setSearch={setsearchTerm}
disableSearchButton
searchMessage={homesMessage}
disableColumnMenu
hideFilters
mui
/>
</>
);
});
export default Properties;
I found the same error. but I am using the table for showing details and also using with it to change the table pages. Here, if I pass just count={orders.data.total} I am facing the error. But when I am setting a default value 0 or any number then it is fixed.
someting like count={orders.data.total || 0}
<TablePagination
className={classes.tablePagination}
rowsPerPageOptions={[10, 25, 50, 100]}
component="div"
count={orders?.data?.total || 0}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
I am trying to have a single onChange event for each element that is rendered with the map function. The problem is that for each letter that I write, it is as if the execution stops, preventing me from writing freely and seeing the changes I am making in the text field in the console.
Here you can see the error: https://codesandbox.io/s/tender-wu-kuzqd
const options = [
{ id: 1, hour: 0, minute: 0, enrollment: '', value: 'ac.terapeutico', label: 'Ac. Terapéutico', color: getRandomColor() },
{ id: 2, hour: 0, minute: 0, enrollment: '', value: 'kinesiologia', label: 'Kinesiología', color: getRandomColor() },
{ id: 3, hour: 0, minute: 0, enrollment: '', value: 'oncologia', label: 'Oncología', color: getRandomColor() }
];
const Form = () => {
const [selectedSpeciality, setSelectedSpeciality] = useState(null);
const handleChangeEnrollment = (value, id) => {
let index = selectedSpeciality.findIndex(el => el.id === id);
const data = [...selectedSpeciality];
data[index].enrollment = value;
setSelectedSpeciality(data);
}
return (
<Grid container spacing={2}>
<Grid item xs={12}>
<Select
closeMenuOnSelect={true}
isMulti
options={options}
styles={colourStyles}
value={selectedSpeciality}
onChange={setSelectedSpeciality}
placeholder='Seleccione especialidades'
noOptionsMessage={() => "No hay más opciones"}
/>
</Grid>
{selectedSpeciality &&
selectedSpeciality.map(el => (
<Fragment key={uuid()}>
<Grid item xs={6}>
<TextField
fullWidth
InputLabelProps={{
shrink: true,
}}
value={el.enrollment}
onChange={(e) => handleChangeEnrollment(e.target.value, el.id)}
label={`MATRÍCULA ${el.label.toUpperCase()}`}
/>
</Grid>
</Fragment>
))
}
{console.log(selectedSpeciality)}
</Grid>
);
}
The problem is that you are using Fragment with uuid, which create a new instance at each render.
This results in rendering the whole part again and losing focus.
You need to remove Fragment or use any other consistent key value for each element in map.
Without Fragment
See: https://codesandbox.io/s/lucid-field-crolh?file=/src/App.js
import React, { useState, Fragment } from "react";
import uuid from "react-uuid";
import styled from "styled-components";
import Select from "react-select";
import { Grid, TextField, Box } from "#material-ui/core";
export const StyleWrapper = styled.div`
.MuiInputBase-root {
font-size: 24px;
font-weight: 600;
color: #061655;
}
`;
const dot = (color = "#ccc") => ({
alignItems: "center",
display: "flex",
":before": {
backgroundColor: color,
borderRadius: 10,
content: '" "',
display: "block",
marginRight: 4,
marginLeft: 8,
height: 10,
width: 10
}
});
const colourStyles = {
control: (styles) => ({ ...styles, backgroundColor: "white" }),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused
? "000000"
: null,
color: isDisabled
? "#ccc"
: isSelected
? "000000" > 2
? "white"
: "black"
: "000000",
cursor: isDisabled ? "not-allowed" : "default",
":active": {
...styles[":active"],
backgroundColor: !isDisabled && (isSelected ? "000000" : "000000")
},
borderRadius: "10px"
};
},
placeholder: (styles) => ({ ...styles, ...dot() }),
multiValue: (styles, { data }) => ({
...styles,
...dot(data.color),
borderRadius: "40px"
}),
multiValueLabel: (styles) => ({
...styles,
color: "#061655",
fontWeight: 500
}),
multiValueRemove: (styles, { data }) => ({
...styles,
color: "#061655",
padding: "8px 8px 8px 4px",
":hover": {
backgroundColor: "#transparent",
color: "#061655",
cursor: "pointer",
borderRadius: "0px 40px 40px 0px"
}
})
};
const FormAddProfessional = (props) => {
const [selectedSpeciality, setSelectedSpeciality] = useState(null);
const optionsSpeciality = [
{
id: 1,
hour: 0,
minute: 0,
enrollment: "",
value: "ac.terapeutico",
label: "Ac. Terapéutico"
},
{
id: 2,
hour: 0,
minute: 0,
enrollment: "",
value: "kinesiologia",
label: "Kinesiología"
},
{
id: 3,
hour: 0,
minute: 0,
enrollment: "",
value: "oncologia",
label: "Oncología"
}
];
const handleChangeEnrollment = (value, id) => {
let index = selectedSpeciality.findIndex((el) => el.id === id);
const data = [...selectedSpeciality];
data[index].enrollment = value;
setSelectedSpeciality(data);
};
return (
<div style={{ width: "100%" }}>
<div style={{ paddingLeft: 4 }}>
<Box borderLeft={1} borderColor="#EBEBEB" style={{ padding: 12 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Select
closeMenuOnSelect={true}
isMulti
options={optionsSpeciality}
styles={colourStyles}
value={selectedSpeciality}
onChange={setSelectedSpeciality}
placeholder="Seleccione especialidades"
noOptionsMessage={() => "No hay más opciones"}
/>
</Grid>
{selectedSpeciality &&
selectedSpeciality.map((el) => (
<Grid item xs={6}>
<TextField
fullWidth
InputLabelProps={{
shrink: true
}}
value={el.enrollment}
onChange={(e) =>
handleChangeEnrollment(e.target.value, el.id)
}
label={`MATRÍCULA ${el.label.toUpperCase()}`}
/>
</Grid>
))}
{console.log(selectedSpeciality)}
</Grid>
</Box>
</div>
</div>
);
};
export default FormAddProfessional;
With different consistent key value
See: https://codesandbox.io/s/angry-jang-8w004?file=/src/App.js:0-4014
import React, { useState, Fragment } from "react";
import uuid from "react-uuid";
import styled from "styled-components";
import Select from "react-select";
import { Grid, TextField, Box } from "#material-ui/core";
export const StyleWrapper = styled.div`
.MuiInputBase-root {
font-size: 24px;
font-weight: 600;
color: #061655;
}
`;
const dot = (color = "#ccc") => ({
alignItems: "center",
display: "flex",
":before": {
backgroundColor: color,
borderRadius: 10,
content: '" "',
display: "block",
marginRight: 4,
marginLeft: 8,
height: 10,
width: 10
}
});
const colourStyles = {
control: (styles) => ({ ...styles, backgroundColor: "white" }),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused
? "000000"
: null,
color: isDisabled
? "#ccc"
: isSelected
? "000000" > 2
? "white"
: "black"
: "000000",
cursor: isDisabled ? "not-allowed" : "default",
":active": {
...styles[":active"],
backgroundColor: !isDisabled && (isSelected ? "000000" : "000000")
},
borderRadius: "10px"
};
},
placeholder: (styles) => ({ ...styles, ...dot() }),
multiValue: (styles, { data }) => ({
...styles,
...dot(data.color),
borderRadius: "40px"
}),
multiValueLabel: (styles) => ({
...styles,
color: "#061655",
fontWeight: 500
}),
multiValueRemove: (styles, { data }) => ({
...styles,
color: "#061655",
padding: "8px 8px 8px 4px",
":hover": {
backgroundColor: "#transparent",
color: "#061655",
cursor: "pointer",
borderRadius: "0px 40px 40px 0px"
}
})
};
const FormAddProfessional = (props) => {
const [selectedSpeciality, setSelectedSpeciality] = useState(null);
const optionsSpeciality = [
{
id: 1,
hour: 0,
minute: 0,
enrollment: "",
value: "ac.terapeutico",
label: "Ac. Terapéutico"
},
{
id: 2,
hour: 0,
minute: 0,
enrollment: "",
value: "kinesiologia",
label: "Kinesiología"
},
{
id: 3,
hour: 0,
minute: 0,
enrollment: "",
value: "oncologia",
label: "Oncología"
}
];
const handleChangeEnrollment = (value, id) => {
let index = selectedSpeciality.findIndex((el) => el.id === id);
const data = [...selectedSpeciality];
data[index].enrollment = value;
setSelectedSpeciality(data);
};
return (
<div style={{ width: "100%" }}>
<div style={{ paddingLeft: 4 }}>
<Box borderLeft={1} borderColor="#EBEBEB" style={{ padding: 12 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Select
closeMenuOnSelect={true}
isMulti
options={optionsSpeciality}
styles={colourStyles}
value={selectedSpeciality}
onChange={setSelectedSpeciality}
placeholder="Seleccione especialidades"
noOptionsMessage={() => "No hay más opciones"}
/>
</Grid>
{selectedSpeciality &&
selectedSpeciality.map((el) => (
<Fragment key={el.value}>
<Grid item xs={6}>
<TextField
fullWidth
InputLabelProps={{
shrink: true
}}
value={el.enrollment}
onChange={(e) =>
handleChangeEnrollment(e.target.value, el.id)
}
label={`MATRÍCULA ${el.label.toUpperCase()}`}
/>
</Grid>
</Fragment>
))}
{console.log(selectedSpeciality)}
</Grid>
</Box>
</div>
</div>
);
};
export default FormAddProfessional;
as i am new in react native. i have no much knowledge of class component. i was stuck in code as class components are used in this code but i want to convert them into functional components. anyone please help me to convert this given code into functional component. this is a code of a swipeable card in react native all the given code in class component and use of constructor and this. i want to just convert it into functional component.
//This is an example of Tinder like Swipeable Card//
import React, { Component } from 'react';
//import react in our code.
import {
Platform, StyleSheet, View, Text,
Dimensions, Animated, PanResponder,
} from 'react-native';
//import all the components we are going to use.
const SCREEN_WIDTH = Dimensions.get('window').width;
class SwipeableCard extends React.Component {
constructor() {
super();
this.panResponder;
this.state = {
Xposition: new Animated.Value(0),
RightText: false,
LeftText: false,
};
this.Card_Opacity = new Animated.Value(1);
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderMove: (evt, gestureState) => {
this.state.Xposition.setValue(gestureState.dx);
if (gestureState.dx > SCREEN_WIDTH - 250) {
this.setState({
RightText: true,
LeftText: false,
});
} else if (gestureState.dx < -SCREEN_WIDTH + 250) {
this.setState({
LeftText: true,
RightText: false,
});
}
},
onPanResponderRelease: (evt, gestureState) => {
if (
gestureState.dx < SCREEN_WIDTH - 150 &&
gestureState.dx > -SCREEN_WIDTH + 150
) {
this.setState({
LeftText: false,
RightText: false,
});
Animated.spring(
this.state.Xposition,
{
toValue: 0,
speed: 5,
bounciness: 10,
},
{ useNativeDriver: true }
).start();
} else if (gestureState.dx > SCREEN_WIDTH - 150) {
Animated.parallel(
[
Animated.timing(this.state.Xposition, {
toValue: SCREEN_WIDTH,
duration: 200,
}),
Animated.timing(this.Card_Opacity, {
toValue: 0,
duration: 200,
}),
],
{ useNativeDriver: true }
).start(() => {
this.setState({ LeftText: false, RightText: false }, () => {
this.props.removeCard();
});
});
} else if (gestureState.dx < -SCREEN_WIDTH + 150) {
Animated.parallel(
[
Animated.timing(this.state.Xposition, {
toValue: -SCREEN_WIDTH,
duration: 200,
}),
Animated.timing(this.Card_Opacity, {
toValue: 0,
duration: 200,
}),
],
{ useNativeDriver: true }
).start(() => {
this.setState({ LeftText: false, RightText: false }, () => {
this.props.removeCard();
});
});
}
},
});
}
render() {
const rotateCard = this.state.Xposition.interpolate({
inputRange: [-200, 0, 200],
outputRange: ['-20deg', '0deg', '20deg'],
});
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
styles.card_Style,
{
backgroundColor: this.props.item.backgroundColor,
opacity: this.Card_Opacity,
transform: [
{ translateX: this.state.Xposition },
{ rotate: rotateCard },
],
},
]}>
<Text style={styles.Card_Title}> {this.props.item.card_Title} </Text>
{this.state.LeftText ? (
<Text style={styles.Left_Text_Style}> Left Swipe </Text>
) : null}
{this.state.RightText ? (
<Text style={styles.Right_Text_Style}> Right Swipe </Text>
) : null}
</Animated.View>
);
}
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
Sample_Card_Array: [{
id: '1', card_Title: 'Card 1', backgroundColor: '#FFC107',
}, {
id: '2', card_Title: 'Card 2', backgroundColor: '#ED2525',
}, {
id: '3', card_Title: 'Card 3', backgroundColor: '#E7088E',
}, {
id: '4', card_Title: 'Card 4', backgroundColor: '#00BCD4',
}, {
id: '5', card_Title: 'Card 5', backgroundColor: '#FFFB14',
}],
No_More_Card: false,
};
}
componentDidMount() {
this.setState({
Sample_Card_Array: this.state.Sample_Card_Array.reverse(),
});
if (this.state.Sample_Card_Array.length == 0) {
this.setState({ No_More_Card: true });
}
}
removeCard = id => {
this.state.Sample_Card_Array.splice(
this.state.Sample_Card_Array.findIndex(x => x.id == id),
1
);
this.setState({ Sample_Card_Array: this.state.Sample_Card_Array }, () => {
if (this.state.Sample_Card_Array.length == 0) {
this.setState({ No_More_Card: true });
}
});
};
render() {
return (
<View style={styles.MainContainer}>
{this.state.Sample_Card_Array.map((item, key) => (
<SwipeableCard
key={key}
item={item}
removeCard={this.removeCard.bind(this, item.id)}
/>
))}
{this.state.No_More_Card ? (
<Text style={{ fontSize: 22, color: '#000' }}>No Cards Found.</Text>
) : null}
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: Platform.OS === 'ios' ? 20 : 0,
},
card_Style: {
width: '75%',
height: '45%',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
borderRadius: 7,
},
Card_Title: {
color: '#fff',
fontSize: 24,
},
Left_Text_Style: {
top: 22,
right: 32,
position: 'absolute',
color: '#fff',
fontSize: 20,
fontWeight: 'bold',
backgroundColor: 'transparent',
},
Right_Text_Style: {
top: 22,
left: 32,
position: 'absolute',
color: '#fff',
fontSize: 20,
fontWeight: 'bold',
backgroundColor: 'transparent',
},
});
the part in the render method is what you return.
to create stateObjects in functional components you will need to use the useState method
const functionalComponent = (props)=>{//props are passed in via props arg...
const defaultState = Xposition: new Animated.Value(0),
RightText: false,
LeftText: false
}
const [state,setState] = useState(defaultState);
... // more stuff
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
styles.card_Style,
{
backgroundColor: props.item.backgroundColor,
opacity: Card_Opacity,
transform: [
{ translateX: state.Xposition },
{ rotate: rotateCard },
],
},
]}>
<Text style={styles.Card_Title}> {props.item.card_Title} </Text>
{this.state.LeftText ? (
<Text style={styles.Left_Text_Style}> Left Swipe </Text>
) : null}
{this.state.RightText ? (
<Text style={styles.Right_Text_Style}> Right Swipe </Text>
) : null}
</Animated.View>
);
}
you should really go watch some videos on useState, you can be much more granular
to set the state you will need to use the setState method returned from the useState call : setState({..state,{XPosition:55}) or something ... you do the ...state to include the old state values, as the state variable will be overwritten with exactly what you pass in... it wont "update" the existing state it will overwrite it
the next bit is hooking into the functionality in componentDidMount you can do this with useEffect
useEffect(()=>{ // this is setup
// do the stuff from componentDidMount
return ()=>{
// any required teardown can be done here
},[] //[] signifies only do this when component mounts... not every update
);// end useEffect componentDidMount
again there is alot more to useEffect, if you want to do stuff when specific state or props are updated
a guy helped me with this code
import React, { useEffect, useState } from "react";
import _ from "lodash";
// const SeleccionClientes = "";
const items = [
{
client: "Microsoft",
idClient: 0,
idProjectType: 1,
projectType: "traditional",
title: "React Native App"
},
{
client: "Amazon",
idClient: 1,
idProjectType: 1,
projectType: "traditional",
title: "ServerSide OPS"
},
{
client: "KFC",
idClient: 2,
idProjectType: 4,
projectType: "traditional",
title: "QR Reader"
},
{
client: "KFC",
idClient: 2,
idProjectType: 1,
projectType: "traditional",
title: "React Native App"
},
{
client: "KFC",
idClient: 2,
idProjectType: 1,
projectType: "traditional",
title: "React KKL"
},
{
client: "PEICI",
idClient: 3,
idProjectType: 1,
projectType: "traditional",
title: "KuKluxKlan"
}
];
export default function ListView() {
const [list, setList] = useState(items);
const [idClient, setIdClient] = useState(2);
const displayProjectsForClient = idClient => {
return list.filter(item => item.idClient === idClient);
};
const displayedProjects = displayProjectsForClient(idClient);
// equivalent to componentDidMount()
useEffect(() => {
setList(displayedProjects);
}, []);
const updateFav = (val, ind) => {
const tempData = _.cloneDeep(list);
tempData[ind].fav = val;
setList(tempData);
};
const favItems = _.filter(list, item => item.fav);
const finalObject = { [new Date().toISOString()]: favItems };
return (
<div>
Selected Client: "KFC"
<br />
Add Favorite Projects:
{displayedProjects.map((item, index) => {
return (
<div
key={index}
style={{ margin: "5px", padding: "5px", background: "#D6D6D6" }}
>
<div>{item.title}</div>
{`Project ID ${item.idProjectType}`}
<input
type="checkbox"
value={item.fav}
onChange={e => updateFav(e.target.checked, index)}
/>
</div>
);
})}
<div>
Active projects (final object): <br />
{JSON.stringify(finalObject, null, 2)}
</div>
</div>
);
}
instead of input checkbox, i'm using the react-native-elements switch, but is not working, i'm assuming is due to a non existing fav inside the item object
this is my code
<FlatList
data={dataSource}
renderItem={({item, index}) => (
<ListItem
containerStyle={{backgroundColor: '#fafafa', width: wp('87.1%'), height: 64, alignItems: 'center', justifyContent: 'center', alignSelf: 'center', marginTop: hp('2.8%'), paddingHorizontal: 0}}
topDivider={false}
bottomDivider={true}
titleStyle={{
marginLeft: 0,
fontSize: rfv(16),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#707070"
}}
subtitleStyle={{
marginLeft: 0,
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#c4c4c4"
}}
title={`${item.title}`}
subtitle={`ID ${item.idCliente}`}
switch={{
trackColor: { false: "#767577", true: "#81b0ff" },
thumbColor: item.fav == true ? "#1062cc" : "#f4f3f4",
ios_backgroundColor: "#9e9e9e",
value: item.fav == undefined ? false : true,
onValueChange: () => {e => console.log(updateFav(e.target.checked == undefined ? false : true, index))}
}}
/>
)}
/>
the idea is to list the projects, which is doing, but when i click on a switch, it creates a whole new object based on that "selection", problem is, the switches are getting to the original position immediately,
forgot to mention, this is the function
const updateFav = (value, index) => {
const tempData = _.cloneDeep(dataSource);
tempData[index].fav = value;
setDataSource(tempData);
};
const favItems = _.filter(dataSource, item => item.fav);
You are using a mixture of React (Html) and React native thats the problem. You will have to change your Switch like this for the function to work. And no need to check for true false as well.
switch={{
trackColor: { false: "#767577", true: "#81b0ff" },
thumbColor: item.fav == true ? "#1062cc" : "#f4f3f4",
ios_backgroundColor: "#9e9e9e",
value: item.fav,
onValueChange: () => {updateFav(!item.fav, index)}
}}