Show component when onClick method is called - reactjs

I have imported a schedular component from devexpress and have modified the appointments in this with the constant MyAppointment. Now i want to be able to delete and modify apointment data with a dialog when clicking on these. Therefore i added an onClick method to the MyAppointment const and tried to return the dialog however nothing happens when pressing the appointments.
const MyAppointment = ({ children, style, ...restProps }) => {
return (
<Appointments.Appointment
{...restProps}
style={{
...style,
backgroundColor: "#a02d37",
borderRadius: "8px",
}}
onClick={()=>{
return (
<SimpleDialogDemo />
)
}}
>
{children}
</Appointments.Appointment>
);
}
const emails = ['username#gmail.com', 'user02#gmail.com'];
function SimpleDialog(props) {
const { onClose, selectedValue, open } = props;
const handleClose = () => {
onClose(selectedValue);
};
const handleListItemClick = (value) => {
onClose(value);
};
return (
<Dialog onClose={handleClose} open={open}>
<DialogTitle>Set backup account</DialogTitle>
<List sx={{ pt: 0 }}>
{emails.map((email) => (
<ListItem button onClick={() => handleListItemClick(email)} key={email}>
<ListItemAvatar>
<Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
<ListItem autoFocus button onClick={() => handleListItemClick('addAccount')}>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Add account" />
</ListItem>
</List>
</Dialog>
);
}
SimpleDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
selectedValue: PropTypes.string.isRequired,
};
export default function SimpleDialogDemo() {
const [open, setOpen] = React.useState(true);
const [selectedValue, setSelectedValue] = React.useState(emails[1]);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = (value) => {
setOpen(false);
setSelectedValue(value);
};
handleClickOpen();
return (
<div>
<SimpleDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
/>
</div>
);
}

onClick={()=>{
return (
<SimpleDialogDemo />
)
}}
isn't actually doing anything. All you're doing is returning a component in an event handler that doesn't do anything with that return value. The onClick event handler is react-agnostic.
If you want to display a component when something is clicked, you'll need to use local state variables to show/hide it based on a click action like below:
const MyAppointment = ({ children, style, ...restProps }) => {
const [showDialog, setShowDialog] = useState(false);
return (
<Appointments.Appointment
{...restProps}
style={{
...style,
backgroundColor: "#a02d37",
borderRadius: "8px",
}}
onClick={()=> setShowDialog(true)}
>
{children}
{showDialog && <SimpleDialogDemo onClose=(() => setShowDialog(false))/>}
</Appointments.Appointment>
);
}
Note the onClose prop I added to your SimpleDialog component so that you have a way of setting the showDialog state back to hidden (you will need to add this propr if you use it).

Related

React component doesn't render on MUI MenuItem onClick event

I have a simple mui Menu, where one MenuItem should render another React component. The problem is that my Menu is rendered in another file, where close and handleClick functions are defined.
Problem: The component doesn't render on the MenuItem click. Seems like it is because setAnchor(null); in the App component sets the anchor to null always. Does this mean I need to use a different anchor? If yes, how?
The Menu component code is as follows:
interface Props {
handler: Handler;
anchor: HTMLButtonElement | null;
onClose: () => void;
}
const AddDataMenu = ({ handler,anchor, onClose }: Props) => {
const renderDataPopOver = () => {
console.log('this is clicked'); <<<<<<<<<< I can see this function is accessed
<AddDataPopover handler={handler} anchor={anchor} onClose={onClose} />;
};
return (
<div>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={onClose}
sx={{ width: 320, maxWidth: '100%' }}
>
<MenuItem onClick={renderDataPopOver}>
<ListItemIcon>
<DataIcon />
</ListItemIcon>
<Typography>item 1</Typography>
</MenuItem>
</Menu>
</div>
);
};
export default AddDataMenu;
This is the Main Component where my Menu is rendered.
const App = ({ scope }) => {
const ref = useRef<HTMLButtonElement>(null);
const [anchor, setAnchor] = useState<HTMLButtonElement | null>(null);
const [handler, setHandler] = useState<Handler>();
const close = () => { <<<<<<< this is accessed before MenuItem click
setAnchor(null);
};
const handleClick = () => { <<<<<<< this is accessed before MenuItem click
setAnchor(ref.current);
};
return showAdd && handler ? (
<MessageContainer
message={'test'}
actions={
<Box ml={1}>
<Button ref={ref} color="primary" variant="contained" onClick={handleClick}>
{t('Visualization.chart-initial-states.AddColumns')}
</Button>
<AddDataMenu handler={handler} anchor={anchor} onClose={close} />
</Box>
}
/>
) : (
<DisplayError />
);
};
export default App;
Assuming that the goal is to render a secondary Popover beside Menu on click of MenuItem, perhaps indeed the component would need to assign any MenuItem that triggers it as anchorEl to the rendered Popover.
Basic live example on: stackblitz (this and below examples omitted everything except for the Menu from the original posted code for simplicity).
In AddDataMenu, add AddDataPopover to the output with its initial anchorEl as null so it would not render immediately. Matching anchorEl can be assigned in the event of handleOpen.
A ref array is used to reference multiple MenuItem here, but this is an optional approach.
const AddDataMenu = ({ anchor, onClose }) => {
const [itemAnchor, setItemAnchor] = useState(null);
const itemRef = useRef([]);
const handleClose = () => {
setItemAnchor(null);
};
const handleOpen = (index) => {
setItemAnchor(itemRef.current[index]);
};
return (
<>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={onClose}
sx={{
width: 320,
maxWidth: '100%',
}}
>
{[1, 2, 3].map((item, index) => (
<MenuItem
ref={(node) => (itemRef.current[index] = node)}
key={index}
onClick={() => handleOpen(index)}
sx={{ p: 2 }}
>
<ListItemIcon>
<DataIcon />
</ListItemIcon>
<Typography>{`item ${item}`}</Typography>
</MenuItem>
))}
</Menu>
<AddDataPopover anchor={itemAnchor} onClose={handleClose} />
</>
);
};
In AddDataPopover, wire up the anchorEl to the Popover and style the component to be rendered beside active MenuItem.
const AddDataPopover = ({ anchor, onClose }) => {
return (
<Popover
id={'my-popover'}
open={Boolean(anchor)}
anchorEl={anchor}
onClose={onClose}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'center',
horizontal: 'left',
}}
>
<Typography sx={{ p: 2 }}>The content of Data Popover.</Typography>
</Popover>
);
};

Styling selected LIstItemButton MUI not working

According to the documentation, I can override the style of the selected class by passing a new class under .MuiSelected. something like below:
const useStyles = makeStyles(() => ({
selectedLink: {
"&.Mui-selected": {
backgroundColor: "red",
},
},
}));
Then, I use it:
const MainDrawerMenu: React.FC = () => {
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const classes = useStyles();
// Responsive swipe on mobile
const iOS =
typeof navigator !== "undefined" &&
/iPad|iPhone|iPod/.test(navigator.userAgent);
const handleSelected = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
selectedIndex: number
) => {
setSelectedIndex(selectedIndex);
};
return (
<React.Fragment>
<SwipeableDrawer
classes={{ paper: classes.drawerMenuHolder }}
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={isDrawerOpen}
onClose={() => {
setIsDrawerOpen(false);
}}
onOpen={() => {
setIsDrawerOpen(true);
}}>
<List disablePadding>
<ListItemButton
component={Link}
classes={{ selected: classes.selectedLink }}
to='/'
onClick={(event) => {
setIsDrawerOpen(false);
handleSelected(event, 0);
}}
selected={selectedIndex === 0}>
<ListItemText disableTypography className={classes.drawerItem}>
Home
</ListItemText>
</ListItemButton>
</List>
</SwipeableDrawer>
<IconButton
className={classes.iconMenuBtn}
onClick={() => {
setIsDrawerOpen(!isDrawerOpen);
}}>
<MenuIcon fontSize='large' className={classes.menuIcon} />
</IconButton>
</React.Fragment>
);
};
Yet, it doesn't work. I can see it in the dev tools, but for some reason it gets overridden by another class. See screenshot. I have tried also creating a new class with the css naming convention but no luck...
Solution below:
<ListItemButton sx={{
'&.Mui-selected': {
backgroundColor: 'rgba(220, 0, 50, 0.1)'
}
}} selected={0}>
<ListItemText primary={'label in item selected'} />
</ListItemButton>

Mui custom step not rendering

I'm trying to use the mui stepper but I have a problem, my step does not render.
My stepper is in a dialog :
const DialogForm = ({ open, activeStep, children }: {
open: boolean;
activeStep: number;
children: ReactNode | ReactNode[];}
) => {
const [isOpen, setIsOpen] = useState(open);
return (
<Dialog
open={isOpen}
PaperProps={{
sx: dialogPaperStyle,
}}
>
<Stepper activeStep={activeStep} connector={null} orientation="vertical" sx={{ height: '100%' }}>
{children}
</Stepper>
</Dialog>
);
};
DialogForm.Step = QCStep;
And each step body is a children of :
const QCStep = ({ children }: {children: ReactNode | ReactNode[];}) => {
const stepProps: { completed?: boolean } = {};
console.log(children);
return (
<Step {...stepProps}>
<StepContent sx={stepContentStyle}>
<> {children} </>
</StepContent>
</Step>
);
};
And I try to create my steps but they don’t appear, but if I delete my stepContent, all my steps appear afterwards
<DialogForm open={true} activeStep={activeStep}>
<DialogForm.Step>
{'COUCOU'}
</QCDialogForm.Step>
<DialogForm.Step>
<TextField placeholder={'placeholder'} />
</DialogForm.Step>
</DialogForm>
I've find a solution by mapping my children and by putting my step and stepContent components in the same file as the stepper component:
const DialogForm = ({ open, activeStep, children }: {
open: boolean;
activeStep: number;
children: ReactNode | ReactNode[];}
) => {
const [isOpen, setIsOpen] = useState(open);
return (
<Dialog
open={isOpen}
PaperProps={{
sx: dialogPaperStyle,
}}
>
<Stepper activeStep={activeStep} connector={null} orientation="vertical" sx={{ height: '100%' }}>
{children.map((child, index) => {
return (
<Step key={index} active={index === activeStep}>
<StepContent sx={stepContentStyle} TransitionComponent={Collapse}>
{child}
</StepContent>
</Step>
);
})}
</Stepper>
</Dialog>
);
};
And my children are all my steps

How to disable Mui-focusVisible Mui-focused for an AccordionSummary element?

I'm using a custom element for changing the group name of an AccordionSummary.
The UI in normal mode
Element code
Upon clicking the edit button for changing name, mui-focused mui-focusVisible property is passed to AccordionSummary which creates a grey background over the element. Upon clicking save, the mui-focused property is removed again. However, when trying to submit pressing enter and not clicking save, mui-focused stays and AccordionSummary is greyed/focused even in non-edit mode.
Mui-focused upon edit
Mui-focused/focusVisible element code
Greyed UI
I have also tried using a modal form instead for changing the group name. Got the exact same issue of a persistent mui-focused upon submitting by pressing enter.
Is there any way where I could override/disable focused property? I don't want to preventDefault for enter keypress because of bad UX. Sharing the relevant code below.
const AccordionSummary = withStyles((theme) => ({
content: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
'&$expanded': {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
},
expanded: {},
}))(MuiAccordionSummary)
const useStyles = makeStyles((theme) => ({
turbineIcon: {
marginRight: theme.spacing(2),
},
gap: {
gap: theme.spacing(1),
},
}))
const GroupSummary = ({
onChangeGroupParameter,
}) => {
const classes = useStyles()
const handleGroupNameValidation = (newName) => {
return /^[A-Za-z][A-Za-z0-9\s_\-]+$/.test(newName)
}
const handleGroupNameChange = useCallback(
(newGroupName) => {
onChangeGroupParameter(groupId, 'group', newGroupName)
},
[onChangeGroupParameter, groupId]
)
return (
<AccordionSummary
aria-controls={`${groupId}-content`}
id={`${groupId}-header`}
>
<Box
display="flex"
width="100%"
alignItems="center"
justifyContent="center"
flexWrap="wrap"
className={classes.gap}
>
<TurbineIconButton big visual className={classes.turbineIcon} />
<Tooltip title="Add">
<EditableTypography
variant="h4"
initialValue={groupId}
onValidate={handleGroupNameValidation}
onSave={handleGroupNameChange}
label="group name"
/>
</Tooltip>
</Box>
</AccordionSummary>
)
}
EditableTypography-
const useStyles = makeStyles((theme) => ({
noPadding: {
padding: 0,
},
iconStyle: {
width: theme.spacing(2.5),
height: theme.spacing(2.5),
},
}))
const defaultOnValidate = () => true
const EditableTypography = ({
initialValue,
onSave,
onValidate,
textFieldProps,
iconButtonProps,
containerProps,
...rest
}) => {
const classes = useStyles()
const [value, setValue] = useState(initialValue)
const [editing, setEditing] = useState(false)
const handleKeyPress = useCallback(
(e) => {
if (e.key === 'Enter') {
handleValueSave(value)
} else if (e.key === 'Escape') {
setEditing(false)
setValue(initialValue)
}
},
[handleValueSave, setEditing, setValue, initialValue, value]
)
const isValid = useMemo(() => onValidate(value), [onValidate, value])
const getHelperText = useCallback(() => {
if (value === initialValue) return 'No change'
if (!isValid) return 'Invalid'
return ''
}, [isValid, value, initialValue])
const helperText = useMemo(() => getHelperText(value), [getHelperText, value])
const handleValueSave = useCallback(
(value) => {
setEditing(false)
if (isValid) {
onSave(value)
}
},
[setEditing, onSave, isValid]
)
const handleTextFieldOnChange = useCallback((e) => setValue(e.target.value), [
setValue,
])
const handleSaveClick = useCallback(() => handleValueSave(value), [
handleValueSave,
value,
])
const handleEditClick = useCallback(
(e) => {
e.stopPropagation()
setEditing(true)
},
[setEditing]
)
return (
<Box display="flex" alignItems="center" {...containerProps}>
{editing && (
<>
<TextField
autoFocus
helperText={helperText}
error={!isValid}
value={value}
variant="outlined"
onChange={handleTextFieldOnChange}
onKeyDown={handleKeyPress}
className={classes.noPadding}
{...textFieldProps}
/>
<IconButton onClick={handleSaveClick} {...iconButtonProps}>
<SaveIcon size="small" />
</IconButton>
</>
)}
{!editing && (
<>
<Typography {...rest}>{value}</Typography>
<IconButton onClick={handleEditClick} {...iconButtonProps}>
<EditIcon size="small" className={classes.iconStyle} />
</IconButton>
</>
)}
</Box>
)
}
Modal code-
<EditGroupNameModal
show={editGroupOpen}
initialValue={groupId}
onModalClose={handleModalClose}
onSave={handleGroupNameChange}
/>
const EditGroupNameModal = ({ initialValue, show, onModalClose, onSave }) => {
const classes = useStyles()
const handleClose = () => {
onModalClose(false)
}
const handleSubmit = useCallback((data) => {
onSave(data.groupName)
handleClose()
})
return (
<Modal
className={classes.modal}
open={show}
onClose={handleClose}
onModalClose={handleClose}
>
<EditGroupForm
initialValue={initialValue}
onCancel={handleClose}
onSubmit={handleSubmit}
/>
</Modal>
)
}
const EditGroupForm = ({ onCancel, initialValue, onSubmit }) => {
const validationSchema = Yup.object().shape({
groupName: Yup.string()
.required('Group name is required')
.matches(
/^[A-Za-z][A-Za-z0-9\s_\-]+$/,
'Group name should have min 2 characters and no special characters except dash (-)'
),
})
const {
register,
handleSubmit,
formState: { errors, isDirty },
} = useForm({
defaultValues: {
groupName: initialValue,
},
resolver: yupResolver(validationSchema),
})
const classes = useStyles()
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<Paper className={classes.paper}>
<Box
className={classes.rootBox}
display="flex"
flexDirection="column"
>
<Box mb={3}>
<Typography variant="h4">Edit Group Name</Typography>
</Box>
<Box mb={3}>
<TextField
autoFocus
fullWidth
defaultValue={initialValue}
{...register('groupName')}
// onKeyPress={(e) => {
// e.key === 'Enter' && e.preventDefault()
// }}
/>
<span style={{ color: 'red' }}>{errors.groupName?.message}</span>
</Box>
<Box ml="auto">
<Button
style={{ marginRight: '1vw' }}
variant="contained"
onClick={onCancel}
>
Cancel
</Button>
<Button
variant="contained"
color="primary"
type="submit"
disabled={!isDirty}
>
Save
</Button>
</Box>
</Box>
</Paper>
</form>
</div>
)
}

ClickAwayListener component not working with DragDropContext

I made a dropdown using Button and a Popper using Material UI components where you can click on the button and get a list of subjects to choose from. To make the popper disappear either we can click on the button again or use a <ClickAwayListener> component which listens to click event and closes the Popper. Now I've to make the list capable of drag and drop feature so I use the react-beautiful-dnd npm package.But the <ClickAwayListener> doesn't seem to work when I include <DragDropContext>, <Droppable> and <Draggable> components.Can anyone help me figure it out?
Here's the code without drag and drop feature. CodeSandbox link https://codesandbox.io/s/gallant-newton-mfmhd?file=/demo.js
const subjectsFromBackend = [
{ name: "Physics", selected: false },
{ name: "Chemistry", selected: false },
{ name: "Biology", selected: false },
{ name: "Mathematics", selected: false },
{ name: "Computer Science", selected: false },
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
paper: {
marginRight: theme.spacing(2)
}
}));
export default function MenuListComposition() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [subjects, setSubjects] = React.useState(subjectsFromBackend);
const handleToggle = () => {
setOpen(!open);
};
const handleClose = () => {
setOpen(false);
};
const ColumnItem = ({ subjectName, selected }) => {
return (
<>
<Grid container>
<Grid item>
<Checkbox checked={selected} />
</Grid>
<Grid item>{subjectName}</Grid>
</Grid>
</>
);
};
return (
<div className={classes.root}>
<div>
<Button
ref={anchorRef}
onClick={handleToggle}
style={{ width: 385, justifyContent: "left", textTransform: "none" }}
>
{`Subjects Selected`}
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<Paper style={{ maxHeight: 200, overflow: "auto", width: 385 }}>
<ClickAwayListener onClickAway={handleClose}>
<List>
{subjects.map((col, index) => (
<ListItem>
<ColumnItem
subjectName={col.name}
selected={col.selected}
/>
</ListItem>
))}
</List>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
);
}
Here's the same code using drag and drop. CodeSandbox Link https://codesandbox.io/s/material-demo-forked-ertti
const subjectsFromBackend = [
{ name: "Physics", selected: false },
{ name: "Chemistry", selected: false },
{ name: "Biology", selected: false },
{ name: "Mathematics", selected: false },
{ name: "Computer Science", selected: false },
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
paper: {
marginRight: theme.spacing(2)
}
}));
export default function MenuListComposition() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [subjects, setSubjects] = React.useState(subjectsFromBackend);
const handleToggle = () => {
setOpen(!open);
};
const handleClose = () => {
setOpen(false);
};
const ColumnItem = ({ subjectName, selected }) => {
return (
<>
<Grid container>
<Grid item>
<Checkbox checked={selected} />
</Grid>
<Grid item>{subjectName}</Grid>
</Grid>
</>
);
};
const onDragEnd = (result, subjects, setSubjects) => {
const { source, destination } = result;
if (!destination) return;
if (source.droppableId !== destination.droppableId) return;
const copiedItems = [...subjects];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setSubjects(copiedItems);
};
return (
<div className={classes.root}>
<div>
<Button
ref={anchorRef}
onClick={handleToggle}
style={{ width: 385, justifyContent: "left", textTransform: "none" }}
>
{`Subjects Selected`}
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<DragDropContext
onDragEnd={(res) => onDragEnd(res, subjects, setSubjects)}
>
<Paper style={{ maxHeight: 200, overflow: "auto", width: 385 }}>
<ClickAwayListener onClickAway={handleClose}>
<Droppable droppableId={"subjectsColumn"}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
<List>
{subjects.map((col, index) => (
<Draggable
key={col.name}
draggableId={col.name}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ListItem>
<ColumnItem
subjectName={col.name}
selected={col.selected}
/>
</ListItem>
</div>
)}
</Draggable>
))}
</List>
{provided.placeholder}
</div>
)}
</Droppable>
</ClickAwayListener>
</Paper>
</DragDropContext>
</Grow>
)}
</Popper>
</div>
</div>
);
}
I found out that the ClickAwayListener's child component should be wrapped around a div so that the click event could be triggered properly.
You need to have your ClickAwayListener at the top when you are using the Drag and Drop.
return (
<ClickAwayListener
onClickAway={() => {
console.log("i got called");
handleClose();
}}
>
.....
</ClickAwayListener>
Here is the working codesandbox - https://codesandbox.io/s/material-demo-forked-h1o1s?file=/demo.js:4877-4897

Resources