I have made a wrapper for MUI Data Grid Component as below:
Selection.tsx:
import * as React from 'react';
import { DataGrid, faIR, GridSelectionModel } from '#mui/x-data-grid';
import type {} from '#mui/x-data-grid/themeAugmentation';
import { createTheme, makeStyles, ThemeProvider } from '#mui/material/styles';
import { useRef, useState } from "react";
import { TablePagination } from "#mui/material";
import Stack from "#mui/material/Stack";
import DeleteIcon from '#mui/icons-material/Delete';
export default function ControlledSelectionServerPaginationGrid(props: any) {
const theme = createTheme(
{
breakpoints: {
values: {
xs: 300,
sm: 600,
md: 900,
lg: 1200,
xl: 1536
}
},
palette: {
primary: { main: '#1976d2' },
},
components: {
MuiDataGrid: {
styleOverrides: {
root: {
display: "flex",
flexDirection: "column-reverse",
borderRadius: '0',
paddingRight: '0px',
},
menu: {
direction: "rtl",
},
footerContainer: {
borderBottom: '1px solid #e0e0e0'
},
cell: {
textAlign: 'right',
'&:last-child': {
textAlign: 'center',
},
}
}
},
MuiTablePagination: {
styleOverrides: {
root: {
borderRadius: 'none',
direction: 'rtl',
},
displayedRows: {
margin: 0,
direction: 'ltr',
},
actions: {
direction: "ltr"
}
}
},
MuiToolbar: {
styleOverrides: {
root: {
display: 'flex',
flexDirection: 'row-reverse',
}
}
},
MuiTableCell: {
styleOverrides: {
root: {
border: 'none'
}
}
}
},
},
faIR,
);
const [page, setPage] = useState<number>(0);
const { packages, loading } = props.FetchData(page);
const [selected, setSelected] = useState<any>([]);
const [rowsPerPage, setRowsPerPage] = useState<number>(15);
const handleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, pageNumber: number) => {
setPage(pageNumber);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setRowsPerPage(parseInt(event.target.value));
setPage(0);
};
const CustomFooter = () => {
return (
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{
borderBottom: '1px solid #e0e0e0'
}}
>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{
marginRight: '20px',
}}
>
{
selected && selected.length > 0 && (
<DeleteIcon sx={{color: "gray", cursor: 'pointer'}} onClick={() => alert('deleted them all')} />
)
}
</Stack>
<CustomPagination/>
</Stack>
)
}
const CustomPagination = () => {
return (
<TablePagination
count={packages?.meta?.last_page}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
labelRowsPerPage={false}
onRowsPerPageChange={handleChangeRowsPerPage}
rowsPerPageOptions={[15, 30, 45, 60]}
/>
)
}
return (
<div style={{ height: '790px', width: '100%' }}>
<ThemeProvider theme={theme}>
{
loading ? (
<div>
loading
</div>
) : (
<DataGrid
rows={packages.data}
getRowId={(row) => row.uuid}
columns={props.columns}
pagination
components={{Footer: CustomFooter}}
checkboxSelection
disableSelectionOnClick
hideFooterSelectedRowCount
loading={loading}
onSelectionModelChange={(ids) => {
setSelected(ids);
}}
/>
)
}
</ThemeProvider>
</div>
);
}
I have the below hook as well which I'm passing to the above component as FetchData:
usePackages.tsx:
export default function usePackages(page: number) {
const [loading, setLoading] = useState<boolean>(false);
const [packages, setPackages] = useState<any>({
data: [],
meta: {},
links: {}
});
useEffect(() => {
setLoading(true);
getListOfAllPackages(page)
.then(data => {
setPackages({
...data.data,
});
setLoading(false);
})
.catch(error => {
setLoading(false);
});
}, [page]);
return { packages, loading };
Now the way I'm using the above components is just like below:
Packages.tsx:
import usePackages from "../../../hooks/Package/usePackages";
const configValues = (params: any) => {
return `${params.row.config.hdd}, ${params.row.config.cpu}, ${params.row.config.ram}`
}
const columns = [
{
field: "product_name",
headerName: "Product Name",
width: '200',
flex: 1
},
{
field: "name",
headerName: "Name",
width: '400',
flex: 1
},
{
field: "config",
headerName: "Config",
width: '150',
flex: 1,
valueGetter: configValues,
},
{
field: "description",
headerName: "Description",
width: '350',
flex: 1
},
{
field: 'actions',
type: 'Actions',
width: '100',
flex: 1,
},
]
return (
<div style={{ display: 'flex', height: '100%', marginTop: '64px'}}>
<div style={{ flexGrow: 1 }}>
<ControlledSelectionServerPaginationGrid columns={columns} FetchData={usePackages}/>
</div>
</div>
)
Now my Selection.tsx renders nearly 4 times and making the other components open with delay like modals and the Side Nav, I tried to console log everything and found out it's because of my passed hook props.FetchData that Selection.tsx re-renders itself, hence checking usePackages.tsx and finding out nothing is wrong. I've used such pattern previously and didn't confront such related issues.
I'm not sure the exact reason for these rerenders though. But I saw some potential things that may affect it.
First, I think it's better to put some variables like theme outside of your component body. Because on each rerender, this variable recreates.
Then, make sure to memorize some of the functions that you are using inside your component body like CustomPagination and CustomFooter with the useMemo hook. Because these are recreating with every rerender that happens, too.
After that, based on my experience, it's better to not pass functions through props. Because React can not compare functions for previous props and next props for each rerender. It's better to abstract functions and import them into the component file that you need.
Here, you can import usePackages directly from Selection.tsx instead of using it from props.
That's the idea behind hooks.
Related
For example: refer this sample data grid.
Want to add a basic search bar like:here
How can I add in Data Grid MUI.
Please guide.
Just create a search bar using a label and text input. Aftermath you can update your grid according to the string in your search box by the search filtered data.
In datagrid there is prop components and Toolbar in that prop to pass whatever component you want.
components={{ Toolbar: QuickSearchToolbar }}
and pass props from
componentsProps={{
toolbar: {
value: searchText,
onChange: (event: React.ChangeEvent<HTMLInputElement>) => handleSearch
},
}}
This is a good example to check out:
import * as React from 'react';
import IconButton from '#mui/material/IconButton';
import TextField from '#mui/material/TextField';
import {
DataGrid,
GridToolbarDensitySelector,
GridToolbarFilterButton,
} from '#mui/x-data-grid';
import { useDemoData } from '#mui/x-data-grid-generator';
import ClearIcon from '#mui/icons-material/Clear';
import SearchIcon from '#mui/icons-material/Search';
import { createTheme } from '#mui/material/styles';
import { createStyles, makeStyles } from '#mui/styles';
function escapeRegExp(value: string): string {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
const defaultTheme = createTheme();
const useStyles = makeStyles(
(theme) =>
createStyles({
root: {
padding: theme.spacing(0.5, 0.5, 0),
justifyContent: 'space-between',
display: 'flex',
alignItems: 'flex-start',
flexWrap: 'wrap',
},
textField: {
[theme.breakpoints.down('xs')]: {
width: '100%',
},
margin: theme.spacing(1, 0.5, 1.5),
'& .MuiSvgIcon-root': {
marginRight: theme.spacing(0.5),
},
'& .MuiInput-underline:before': {
borderBottom: `1px solid ${theme.palette.divider}`,
},
},
}),
{ defaultTheme },
);
interface QuickSearchToolbarProps {
clearSearch: () => void;
onChange: () => void;
value: string;
}
function QuickSearchToolbar(props: QuickSearchToolbarProps) {
const classes = useStyles();
return (
<div className={classes.root}>
<div>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</div>
<TextField
variant="standard"
value={props.value}
onChange={props.onChange}
placeholder="Search…"
className={classes.textField}
InputProps={{
startAdornment: <SearchIcon fontSize="small" />,
endAdornment: (
<IconButton
title="Clear"
aria-label="Clear"
size="small"
style={{ visibility: props.value ? 'visible' : 'hidden' }}
onClick={props.clearSearch}
>
<ClearIcon fontSize="small" />
</IconButton>
),
}}
/>
</div>
);
}
export default function QuickFilteringGrid() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 100,
maxColumns: 6,
});
const [searchText, setSearchText] = React.useState('');
const [rows, setRows] = React.useState<any[]>(data.rows);
const requestSearch = (searchValue: string) => {
setSearchText(searchValue);
const searchRegex = new RegExp(escapeRegExp(searchValue), 'i');
const filteredRows = data.rows.filter((row: any) => {
return Object.keys(row).some((field: any) => {
return searchRegex.test(row[field].toString());
});
});
setRows(filteredRows);
};
React.useEffect(() => {
setRows(data.rows);
}, [data.rows]);
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
components={{ Toolbar: QuickSearchToolbar }}
rows={rows}
columns={data.columns}
componentsProps={{
toolbar: {
value: searchText,
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
requestSearch(event.target.value),
clearSearch: () => requestSearch(''),
},
}}
/>
</div>
);
}
Currently I have the following layout in in react web page:
The map is an embedded component from an external site. The cards are filtered from a list and it renders the matching items. What I want to do is to make the cards appear on the right of the map instead of below. I've tried with a variety of css and div tricks but can't seem to make it work. Any suggestions are welcome.
Code:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Input } from "semantic-ui-react";
import BasicCard from "../components/BasicCard";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
display: "fixed",
marginTop: "15vh",
paddingLeft: "5vh",
width: "50vh",
},
map: {
marginTop: "5vh",
display: "block",
justifyContent: "left",
alignContent: "left",
},
cardHolder: {
display: "inline",
paddingLeft: "50vh",
justifyContent: "right",
justifyItems: "right",
alignContent: "right",
alignItems: "right",
},
searchBox: {
display: "block",
height: 1,
justifyContent: "left",
justifyItems: "left",
alignContent: "left",
alignItems: "left",
},
}));
export default function Posts() {
const [APIData, setAPIData] = useState([]);
const [filteredResults, setFilteredResults] = useState([]);
const [searchInput, setSearchInput] = useState("");
const classes = useStyles();
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
console.log(searchInput);
searchItems(searchInput);
}, 1000);
axios
.get(
`https://data.gov.sg/api/action/datastore_search?resource_id=cdab7435-7bf0-4fa4-a8bd-6cfd231ca73a&limit=1000`
)
.then((response) => {
setAPIData(response.data.result.records);
console.log(response.data.result.records);
});
return () => clearTimeout(delayDebounceFn);
}, [searchInput]);
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
const searchItems = (searchValue) => {
setSearchInput(searchValue);
if (searchInput !== "") {
const filteredData = APIData.filter((item) => {
console.log(Object.values(item).join("").toLowerCase());
return Object.values(item)
.join("")
.toLowerCase()
.includes(searchInput.toLowerCase());
});
setFilteredResults(filteredData);
} else {
setFilteredResults(APIData);
}
};
return (
<div className={classes.root}>
<Input
icon='search'
placeholder='Search...'
onChange={(e) => setSearchInput(e.target.value)}
className={classes.searchBox}
/>
<iframe
className={classes.map}
width='700'
height='400'
src='https://data.gov.sg/dataset/list-of-verified-public-access-aed-locations/resource/b0f395af-d297-4c22-8523-23fff99b17a7/view/3ea52d19-937b-4b2a-9da0-7b74e1d72005'
frameBorder='10'
>
{" "}
</iframe>
{searchInput.length > 1
? filteredResults.map((item) => {
return (
<BasicCard
variant='outlined'
raised={true}
sx={{
border: 1,
gap: 1,
borderColor: "text.primary",
alignItems: "right",
alignContent: "right",
}}
roadName={item.road_name}
postalCode={item.postal_code}
locationDesc={item.aed_location_description}
floorLevel={item.aed_location_description}
operatingHours={item.aed_location_floor_level}
></BasicCard>
);
})
: APIData.map((item) => {
return;
aaaaa;
})}
</div>
);
}
I have a weird situation right here where I try to build a stepper component within my app.
everything goes well so far but I try to make this stepper to go from Right to left.
things I have tried:
I tried to instal i18next (translation library) and it didn't work out.
I tried to go from 3 down to 2, and it also is not a satisfying result
here is the result:
and here is my code:
import {
makeStyles,
Theme,
createStyles,
withStyles,
} from '#material-ui/core/styles';
import clsx from 'clsx';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import SettingsIcon from '#material-ui/icons/Settings';
import GroupAddIcon from '#material-ui/icons/GroupAdd';
import VideoLabelIcon from '#material-ui/icons/VideoLabel';
import StepConnector from '#material-ui/core/StepConnector';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { StepIconProps } from '#material-ui/core/StepIcon';
const ColorlibConnector = withStyles({
alternativeLabel: {
top: 22,
},
active: {
'& $line': {
backgroundImage:
'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)',
},
},
completed: {
'& $line': {
backgroundImage:
'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)',
},
},
line: {
height: 3,
border: 0,
backgroundColor: '#eaeaf0',
borderRadius: 1,
},
})(StepConnector);
const useColorlibStepIconStyles = makeStyles({
root: {
backgroundColor: '#ccc',
zIndex: 1,
color: '#fff',
width: 50,
height: 50,
display: 'flex',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
},
active: {
backgroundImage:
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)',
},
completed: {
backgroundImage:
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
},
});
function ColorlibStepIcon(props: StepIconProps) {
const classes = useColorlibStepIconStyles();
const { active, completed } = props;
const icons: { [index: string]: React.ReactElement } = {
1: <SettingsIcon />,
2: <GroupAddIcon />,
3: <VideoLabelIcon />,
};
return (
<div
className={clsx(classes.root, {
[classes.active]: active,
[classes.completed]: completed,
})}
>
{icons[String(props.icon)]}
</div>
);
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: '100%',
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
},
button: {
marginRight: theme.spacing(1),
},
instructions: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
stepperContainer: {
width: '100%',
},
contentContainer: {
width: '50%',
display: 'flex',
justifyContent: 'center',
},
})
);
function getSteps() {
return ['Select campaign settings', 'Create an ad group', 'Create an ad'];
}
function getStepContent(step: number) {
switch (step) {
case 0:
return 'Select campaign settings...';
case 1:
return 'What is an ad group anyways?';
case 2:
return 'This is the bit I really care about!';
default:
return 'Unknown step';
}
}
const SignUp: React.FC = () => {
const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0);
const steps = getSteps();
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
const handleReset = () => {
setActiveStep(0);
};
return (
<div className={classes.root}>
<div className={classes.stepperContainer}>
<Stepper
dir="rtl"
alternativeLabel
activeStep={activeStep}
connector={<ColorlibConnector />}
>
{steps.map((label) => (
<Step key={label}>
<StepLabel StepIconComponent={ColorlibStepIcon}>
{label}
</StepLabel>
</Step>
))}
</Stepper>
</div>
<div className={classes.contentContainer}>
<div>
{activeStep === steps.length ? (
<div>
<Typography className={classes.instructions}>
All steps completed - you're finished
</Typography>
<Button onClick={handleReset} className={classes.button}>
Reset
</Button>
</div>
) : (
<div>
<Typography className={classes.instructions}>
{getStepContent(activeStep)}
</Typography>
<div>
<Button
disabled={activeStep === 0}
onClick={handleBack}
className={classes.button}
>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
</Button>
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default SignUp;
Hi I was fiddling around with the material UI sandbox,
here's the sandbox with working left to right
things I've change, were:
line 43 - initate step to 2
line 62 - handleNext, prevActiveStep - 1 (from +1)
line 67 - handleBack, prevActiveStep + 1 (from -1)
line 86 - setActiveStep(2) (from setActiveStep(0))
the Stepper component:
<Stepper activeStep={activeStep}>
{steps.map((label, index) => {
const stepProps: { completed?: boolean } = {
completed: activeStep < index // <<< this setting
};
const labelProps: { optional?: React.ReactNode } = {};
if (isStepOptional(index)) {
labelProps.optional = (
<Typography variant="caption">Optional</Typography>
);
}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps}>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
hope that helps
I read Flex-box: Align last row to grid and trying to see if this will work for me too:
.grid::after {
content: "";
flex: auto;
}
but not sure how to specify this in JS styles that I use in MaterialUI:
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
height: 140,
width: 140,
},
control: {
padding: theme.spacing(2),
},
fullName: {
marginRight: '3px'
}
}));
Like how do I add after to a MaterialUI Grid?
<Grid className={classes.root} container justify="space-around" spacing={2}>
<CompanyList companies={companies} data-test-id="companyList" />
</Grid>
You could use like below:
const useStyles = makeStyles((theme) => ({
grid: {
// --- some styles
'&::after': {
// --- some style
}
},
}));
It's similar task as to add pseudo element to material-ui component
content attribute needed to be double quoted
Working exammple
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
"&::after": {
content: '""',
width: 40,
height: 40,
backgroundColor: "cyan",
color: "white"
}
},
}));
export default function Material() {
const classes = useStyles();
return (
<div>
<h1>Material</h1>
<Grid
className={classes.root}
container
justify="space-around"
spacing={2}
>
<CompanyList companies={companies} data-test-id="companyList" />
</Grid>
</div>
);
}
I have const experience that creates 6 experiences with there popover. I am supposed to add useCallback to it but when I go I get and error.
This is my component experience
import React, { memo, useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/styles';
import Typography from '#material-ui/core/Typography';
import clsx from 'clsx';
import Button from '#material-ui/core/Button';
import Popover from '#material-ui/core/Popover';
import gastronomia from 'assets/experiences/gastronomia.jpg';
import productos from 'assets/experiences/productos.jpg';
import giftcard from 'assets/experiences/giftcard.jpg';
import diversion from 'assets/experiences/diversion.jpg';
import deporte from 'assets/experiences/deporte.jpg';
import belleza from 'assets/experiences/belleza.jpg';
import gastronomiaExperiences from 'data/gastronomia';
import productosExperiences from 'data/productos';
import giftcardExperiences from 'data/giftcard';
import diversionExperiences from 'data/diversion';
import deporteExperiences from 'data/deporte';
import bellezaExperiences from 'data/belleza';
// Proptypes definitions to the component.
const propTypes = {
/** Custom root className. */
className: PropTypes.string,
};
// Default props definitions.
const defaultProps = {
className: null,
};
// Component's styles
const useStyles = makeStyles(theme => ({
root: {
display: 'block',
margin: '0 auto',
maxWidth: '50%',
[theme.breakpoints.down('md')]: {
maxWidth: '70%',
},
[theme.breakpoints.down('sm')]: {
maxWidth: '100%',
},
'& .experiences-column': {
display: 'inline-block',
verticalAlign: 'top',
textAlign: 'center',
'&.col1': {
width: '36.31%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'&.col2': {
width: '63.69%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'& .experience': {
padding: 2,
position: 'relative',
'& img': {
width: '100%',
display: 'block',
},
'& .experience-title': {
position: 'absolute',
bottom: 30,
left: 0,
right: 0,
textAlign: 'center',
},
},
},
},
paper: {
width: '50%',
left: '25% !important',
height: '280px',
'& img': {
width: '100px',
padding: '0 10px 0 10px',
},
},
gastronomia: {
backgroundColor: 'rgba(0,185,208,0.9)',
},
giftcard: {
backgroundColor: 'rgba(221,165,174,0.9)',
},
deporte: {
backgroundColor: 'rgba(189,143,205,0.9)',
},
productos: {
backgroundColor: 'rgba(221,165,174,0.9)',
},
diversion: {
backgroundColor: 'rgba(255,176,10,0.9)',
},
belleza: {
backgroundColor: 'rgba(229,166,187,0.9)',
},
'#media screen and (max-width: 1024px)': {
paper: {
width: '70%',
left: '15% !important',
},
},
'#media screen and (max-width: 480px)': {
paper: {
width: '100%',
left: '0% !important',
height: '350px',
},
},
}), { name: 'ExperiencesStyle' });
*/const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
// const open = Boolean(anchorEl);
const experience = useCallback((img, title, id, popoverCategory, anchorEl, classes, handleClick) => (
<div
className="experience"
aria-describedby={id}
id={id}
onClick={handleClick}
onKeyDown={handleClick}
role="button"
tabIndex="0"
>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
<Popover
id={id}
open={anchorEl && anchorEl.id === id}
anchorEl={anchorEl}
onClose={handleClose}
classes={{paper: clsx(classes.paper, classes[id])}}
>
<div>
<Button onClickAway={handleClose}>x</Button>
<div>
{
popoverCategory.map(url => (
<img
key={url}
data-sizes="auto"
className="lazyload"
src={url}
alt={title}
/>
))
}
</div>
</div>
</Popover>
</div>
), []);
return (
<div className={clsx(classes.root, className)}>
<div className="experiences-column col1">
{experience(gastronomia, 'GASTRONOMÍA', 'gastronomia', gastronomiaExperiences)}
{experience(giftcard, 'GIFT CARD', 'giftcard', giftcardExperiences)}
{experience(deporte, 'DEPORTE', 'deporte', deporteExperiences)}
</div>
<div className="experiences-column col2">
{experience(productos, 'PRODUCTOS', 'productos', productosExperiences)}
{experience(diversion, 'DIVERSIÓN', 'diversion', diversionExperiences)}
{experience(belleza, 'BELLEZA', 'belleza', bellezaExperiences)}
</div>
</div>
);
},
);
and the error is:
TypeError: Cannot read property 'paper' of undefined
referring to this line
classes={{paper: clsx(classes.paper, classes[id])}}
where I add the classes to the paper class of the popover.
I am not used to useCallback and new to react so I am lost.
const experience = useCallback((img, title, id, popoverCategory, anchorEl, classes, handleClick) => (
The function you have created expects 7 things to be passed into it. But when you use it, you only pass in 4:
experience(gastronomia, 'GASTRONOMÍA', 'gastronomia', gastronomiaExperiences)
So the remaining 3 are all undefined inside the function. the anchorEl, classes, and handleClick variables defined at the top of your component are not visible inside experience, because those variable are being "shadowed".
So you could stop shadowing the variables by simply removing the last 3 arguments from your function definition:
const experience = useCallback((img, title, id, popoverCategory) => (
However, i do have to express that useCallback doesn't seem to be doing anything for you. The benefit of useCallback is that you can have the experience variable be the same reference from one render to the next, but that doesn't seem to be a feature that you need. Your component never tries to compare references of experience, nor is experience passed to any other component where it might be checked in a shouldComponentUpdate or React.memo.
So i would recommend deleting the useCallback entirely:
const experience = (image, title, id, popoverCategory) => (