Update list item in material ui? - reactjs

I have a project in react, and I'm building a simple todo app. When an item in the list is clicked, I want to update the value. My code looks like this:
export default function ShowTodos () {
const [todos, setTodos] = React.useState<ITodo[]>([]);
const [selectedTodo, setSelectedTodo] = React.useState<ITodo>({});
const [dialogState, setDialogState] = React.useState<boolean>(false);
const confirm = useConfirm();
useEffect(() => {
getTodos()
.then(({data: {todos}}: ITodo[] | any) => {
const todoList = todos
setTodos(todoList)
})
.catch((err: Error) => console.log(err))
})
const handleConfirmation = (id: string) => {
confirm({ description: 'This action is permanent!' })
.then(() => { deleteTodoById(id).then(r => )})
}
return (
<List>
{todos.map((todo: ITodo ) =>
(
<ListItem onClick={() => {
console.log("hh", todo);
setDialogState(!dialogState)
}
} key={todo._id}>
<ListItemText
primary={todo.text}
/>
<IconButton edge="end" aria-label="delete" onClick={() => handleConfirmation(todo._id)}>
<DeleteIcon />
</IconButton>
<Dialog open={dialogState} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Update</DialogTitle>
<DialogContent>
<TextField
defaultValue={todo.text}
autoFocus
margin="dense"
id="name"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={() => setDialogState(false)}>
Cancel
</Button>
<Button color="primary" onClick={() => updateTodo(selectedTodo)}>
Update
</Button>
</DialogActions>
</Dialog>
</ListItem>
)
)}
</List>
);
}
However the odd thing is that the defaultValue when the item is clicked is always the last item in the list. How am I to change it to be the text of the item clicked?
Thanks!

You need separate state of each todo item to new Component

The main problem in this piece of code:
<ListItem onClick={() => {
console.log("hh", todo);
setDialogState(!dialogState)
}
} key={todo._id}>
onClick you toggling dialog, but not specify selectedTodo.
try something like that:
onClick={() => {
console.log("hh", todo);
setSelectedTodo(todo);
setDialogState(!dialogState);
}
and I guess you should define updateTodo

Related

function remove of react-hook-form removing last element instead of correct one

i am trying to use useFieldsArray from react-hook-form, but when I click on the button to delete, instead of deleting the correct element it always deletes the last one. can someone more experienced give me some advice?
I don't understand what I'm doing wrong.
Thanks in advance.
export const isThisAFieldsArrayContext = React.createContext(false)
export default function FieldsArray({
control,
formNamePrefix,
register,
inputsToRender,
addNewDisabled = false,
watchConfig,
...props
}) {
const classes = useStyles()
const { append, remove } = useFieldArray({
control,
shouldUnregister: false,
name: formNamePrefix,
})
const additionalProps = useWatchForAdditionalProps(watchConfig)
function handleRemove(index) {
remove(index)
}
return (
<isThisAFieldsArrayContext.Provider value={true}>
<Grid item xs={12}>
<List>
{control.getValues(formNamePrefix)?.map((item, index) => {
console.log({ item })
return (
<ListItem key={index} className={classes.listItem}>
<DynamicFormFields
{...props}
register={register}
fieldsKey={`${formNamePrefix}.${index}`}
formFields={inputsToRender}
control={control}
// defaultValue={item.defaultValue}
/>
<DeleteButton
variant="contained"
onClick={() => handleRemove(index)}
aria-label="delete"
className={classes.deleteButton}
>
<DeleteIcon color="secondary" fontSize="small" />
</DeleteButton>
</ListItem>
)
})}
</List>
<Button
variant="contained"
onClick={() => append({})}
aria-label="add"
color="secondary"
className={classes.addButton}
disabled={addNewDisabled || additionalProps.disabled}
>
<AddIcon color="primary" />
</Button>
</Grid>
</isThisAFieldsArrayContext.Provider>
)
useFieldArray returns fields
const { fields, ... } = useFieldArray(...)
Use it to map
fields.map((item, index) => {
// there is an 'id' in item. Use it for key!
return (
<ListItem key={item.id}>
...
<button onClick={() => handleRemove(index)}>Delete</button>
...
</ListItem>
)
})
The value of 'formNamePrefix' has to be an array of objects.
Example:
useForm({
defaultValues: {
[formNamePrefix]: [{name: 'name1'}, {name: 'name2'}, ...]
}
})

Material UI Dialog with checkbox acknowledgement before user can press the yes button to delete record

I am trying to implement a mui dialog box that contains a checkbox that the user has to check/acknowledge first before they can select the "Yes" option.
I currently have a working dialog box component that I am calling that doesn't have the checkbox, but I have added the checkbox part as follows:
export default function ConfirmCheckboxDialog(props) {
const { confirmDialog, setConfirmDialog } = props;
const classes = useStyles();
const [checked, setChecked] = React.useState(false);
const handleChange = (event) => {
setChecked(event.target.checked);
};
return (
<Dialog open={confirmDialog.isOpen} classes={{ paper: classes.dialog }}>
<DialogTitle className={classes.dialogTitle}>
<IconButton disableRipple className={classes.titleIcon} size="large">
<NotListedLocationIcon />
</IconButton>
</DialogTitle>
<DialogContent className={classes.dialogContent}>
<DialogContentText id="alert-dialog-description">
<Typography variant="h6">{confirmDialog.title}</Typography>
<FormControlLabel
control={
<Checkbox checked={checked} onChange={handleChange} />
}
label="Please acknowledge"
/>
</DialogContentText>
</DialogContent>
<DialogActions className={classes.dialogAction}>
<Controls.Button
text="No"
color="primary"
onClick={() => setConfirmDialog({ ...confirmDialog, isOpen: false })}
/>
<Controls.Button text="Yes" color="success" onClick={confirmDialog.onConfirm} />
</DialogActions>
</Dialog>
);
}
Now I am calling this ConfirmCheckboxDialog component above from another component where I have set the following details:
const [confirmDialog, setConfirmDialog] = useState({ isOpen: false, title: '' });
I also have the following button:
<Button
color="secondary"
onClick={() => {
setConfirmDialog({
isOpen: true,
title: `Delete?`,
onConfirm: () => {
console.log("Deleted......";
}
});
>
{<span>Delete</span>}
</Button>
<ConfirmCheckboxDialog confirmDialog={confirmDialog} setConfirmDialog={setConfirmDialog} />
Based on the above ConfirmCheckboxDialog component, I'm not sure how to validate the checkbox against the Yes button as the only means of the onClick={confirmDialog.onConfirm} /> being set is if the checkbox is set/true
Any help would be good.
Try to define a dedicate function to handle the Yes button click:
const handleYes = () => {
if (checked && confirmDialog.onConfirm) {
setConfirmDialog({ ...confirmDialog, isOpen: false })
confirmDialog.onConfirm();
}
}
...
<Controls.Button text="Yes" color="success" onClick={() => handleYes()} />

Open a modal (or dialogue) from a dropdown asking the user if they would like to take an action

I would like to open a modal (or dialogue) when a user selects an option from a dropdown menu.
Eventually there will be a few options in the dropdown, and different dialogues/modals will be called and rendered depending on which dropdown option has been clicked. For now - how do I get the modal/dialogue to open with dropdown menu events?
I'm currently using the handleClose handler to attempt to open a dialogue since that event should be easy to grab and use right out of the docs for MUI Menu and MenuItem.
The origination call to the DropdownMenu component (which works well and shows the dropdown menu) occurs in a table through the click of an icon
<DropdownMenu options={['Show modal or dialogue to the user']}>
<MoreHorizIcon />
</DropdownMenu>
The DropdownMenu component (also working well itself, except for not triggering the dialogue/modal) looks like this
interface IProps extends Omit<unknown, 'children'> {
children: any;
options: string[];
}
const ITEM_HEIGHT = 48;
const DropdownMenu = ({ children, options }: IProps) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const showModal = () => {
return (
<AlertDialog />
)
}
const handleClose = () => {
//the native alert dialogue works well
alert('I want to show a dialog or modal the same way this alert shows up and ask the user if they are sure they want to delete something')
//why isn't the custom alert dialog being called?
showModal();
setAnchorEl(null);
};
return (
<div>
<IconButton
aria-label="more"
id="more-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
{children}
</IconButton>
<Menu
id="dropdownmenu"
MenuListProps={{
'aria-labelledby': 'more-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch'
}
}}
>
{options.map(option => (
<MenuItem key={option} onClick={handleClose} >
{option}
</MenuItem>
))}
</Menu>
</div>
);
};
export default DropdownMenu;
And the example starter dialogue I am using to get the ball rolling looks like this
const AlertDialog = () => {
const [open, setOpen] = React.useState(true);
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Sweet Filler Dialog"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>NO</Button>
<Button onClick={handleClose} autoFocus>
YES
</Button>
</DialogActions>
</Dialog>
</div>
);
}
You can use a state variable to trigger the modal in the DropdownMenu component, whenever you wanted to show the dialog/modal.
const [isModalOpen, setIsModalOpen] = React.useState(false);
and then in the handleClose, you can update the modal state to open the modal.
const handleClose = () => {
setIsModalOpen(true)
setAnchorEl(null);
};
Then somewhere in your JSX of DropdownMenu, you can conditionally render the AlertDialog component like this
{isModalOpen ? <AlertDialog open={isModalOpen} setOpen={setIsModalOpen} /> : null}
Finally, update your AlertDialog component to use props to handle the closing of the modal.
const AlertDialog = ({ open, setOpen }) => {
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Sweet Filler Dialog"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>NO</Button>
<Button onClick={handleClose} autoFocus>
YES
</Button>
</DialogActions>
</Dialog>
</div>
);
}

How can i call a function inside another Component with React?

I have a component (Navigation.js) which imports another component (Dialog.js). I want that if I react to a click event, call a function in the dialog component (handleClickOpen ()). But I don't know how to do that. So what i have to do ?
Navigation.js
export default function SimpleBottomNavigation() {
return (
<BottomNavigation
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
showLabels
className={classes.root}
>
<BottomNavigationAction
label="Home"
onClick={'HERE I WANT TO CALL THE FUNCTION IN THE DIALOG COMPONENT'}
icon={<RestoreIcon />}
/>
<BottomNavigationAction label="Neuer Plan" icon={<FavoriteIcon />} />
<BottomNavigationAction label="Azubis" icon={<LocationOnIcon />} />
</BottomNavigation>
);
}
Dialog.js
export default function CustomizedDialogs() {
const [open, setOpen] = React.useState(false);
*/THIS FUNCTION I WANT TO CALL FROM NAVIGATION.JS */
const handleClickOpen = () => {
setOpen(true);
};
[...]
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open dialog
</Button>
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Modal title
</DialogTitle>
<DialogContent dividers>
</Dialog>
</div>
);
}
There is a aimple way to use child's functions, with functional components it's forwardRef and useImperativeHandle, along those lines:
Navigation (parent)
function Navigation() {
const dialogRef = useRef();
return(
<button onClick={() => dialogRef.current.handleClickOpen()}>
Click me!
</button>
);
}
Dialog (child)
const Dialog = forwardRef((props, ref) => {
useImperativeHandle(ref, () => {
const handleClickOpen = () => {
//your implementation
};
});
return (...);
});
If I over-simplified, please let me know :)

React - how to invoke popup window in my case?

I'm not a React expert yet thus I have a question for you - how to invoke my popup window from:
import {options, columns,convertToArray} from './consts'
const index = () => {
const {data, loading, error, performFetch} = fetchHook({path: "/xxx/yyy", fetchOnMount: true})
return (
<div className={classes.Container}>
<h1>List of products</h1>
<Divider className={classes.Divider} />
<ProductTable data={convertToArray(data)} options={options} columns={columns}/>
</div>
)
}
export default index;
consts.js
export const actions = (productPropertyId, showModal) => {
const productDetails = (productPropertyId) => {
}
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
return (
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
>{"Remove"}
</Button>
</div>
)
};
export const convertToArray = (productList) => {
let products = []
if (productList != null) {
productList.map(product => {
column1, column2, column3, actions(product.id)]
products.push(prod)
})
}
return products;
};
My popup is --> <FormDialog/> based on react Materials.
Is it possible to invoke popup in this place?
I have a react material table with some columns. The last column contains 2 buttons, one of them is "Remove" (row). Here I want to invoke my popup. Maybe I should rebuild my structure?
UPDATE
Below is my popup - I wonder how to run this popup from the place above:
const formDialog = (popupOpen) => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
{/*<Button variant="outlined" color="primary" onClick={handleClickOpen}>*/}
{/* Open alert dialog*/}
{/*</Button>*/}
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send updates
occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default formDialog;
UPDATE 2
I updated my code taking into cosideration the tips from your response, see above. Can I add a parameter showModal in my export const actions = (productPropertyId, showModal) and then invoke this component with different showModal value? UNfortunately my popup doesn't appear when I click on Remove button :(
You can invoke it conditionally and controle it using some state variable. Like this:
const [removeModal, removeToggle] = useState(false);
return (<>
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
onClick={() => removeToggle(true)}
>{"Remove"}
</Button>
</div>
{removeModal ? <YourComponent /> : ''}
</>
)
I'm using a react fragment there <></> just to position the modal div outside the main div, but you can also invoke it inside your main div as well (I usually do this and set the position: fixed so the modal/popup coud appear in top of everything).

Resources