I have a MUI custom dialog, with 1 input text and 2 action buttons (Cancel, Done).
I can pass onClick function etc.. but
I have problem in passing data from this dialog to the actual parent widget, because when I click on "Done" i need to:
save the text
close the dialog
My Dialog
const FormDialog = ({ open, onClose, onSave }) => {
const [value, setValue] = useState("");
const parentToChild = () => {};
if (!open) return null;
return (
<div>
<Dialog open={open} onClose={onClose}>
<DialogTitle>Edit</DialogTitle>
<DialogContent>
<DialogContentText>Add name</DialogContentText>
<TextField
value={value}
onChange={setValue(value)} />
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={() => onSave(value)}>Salva</Button>
</DialogActions>
</Dialog>
</div>
);
and the dialog inside my class:
This is where I should receive the text value
<FormDialog open={open} onClose={() => setOpen(false)} onSave={() => editCategory()} />
const editCategory = (value) => () => {
console.log(value);
setOpen(false);
};
You need to receive the parameter in the onSave parent function
//Before
... onSave={()=>editCategory()}
//After
... onSave={(value)=>editCategory(value)}
But that is not needed since you can pass the function editCategory directly to onSave (editCategory already receives "value"):
... onSave={editCategory}
IMPORTANT
// wrong
const editCategory = (value) => () => {...}
// correct
const editCategory = (value) => {...}
So, finally:
<FormDialog open={open} onClose={() => setOpen(false)} onSave={editCategory} />
const editCategory = (value) => {
console.log(value);
setOpen(false);
};
Related
I have 2 popup's(I reuse CloseButton(component) and Modal(component) in 2 popup's) and need to do focus trap at all. I lf answer 4 better way.
1 popup Screen, components: ModalLogin-Modal-CloseButton.
I read about some hooks: useRef() and forwardRef(props, ref)
but i don't undestand why it's not work in my case. I am trying to find a solution. I need help :)
In ModalLogin, I try to do a focus trap. To do this, I mark what should happen with focus when moving to 1 and the last element. I need to pass my ref hook obtained via Modal-CloseButton. I read that you can't just transfer refs to functional components. I try to use the forwardref hook in the necessary components where I transfer it, here's what I do:
All links without focus-trap and hook's!.
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/form-login/modal-login.jsx [Modal-login full]
const ModalLogin = () => {
const topTabTrap* = useRef();
const bottomTabTrap* = useRef();
const firstFocusableElement = useRef();
const lastFocusableElement = useRef();
useEffect(() => {
const trapFocus = (event) => {
if (event.target === topTabTrap.current) {
lastFocusableElement.current.focus()
}
if (event.target === bottomTabTrap.current) {
firstFocusableElement.current.focus()
}
}
document.addEventListener('focusin', trapFocus)
return () => document.removeEventListener('focusin', trapFocus)
}, [firstFocusableElement, lastFocusableElement])
return (
<Modal onCloseModal={() => onCloseForm()} ref={lastFocusableElement}>
<form >
<span ref={topTabTrap} tabIndex="0" />
<Logo />
<Input id="email" ref={firstFocusableElement} />
<Input id="password" />
<Button type="submit" />
<span ref={bottomTabTrap} tabIndex="0"/>
</form>
</Modal>
);
};
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/modal/modal.jsx [Modal full]
const Modal = forwardRef(({ props, ref }) => {
const { children, onCloseModal, ...props } = props;
const overlayRef = useRef();
useEffect(() => {
const preventWheelScroll = (evt) => evt.preventDefault();
document.addEventListener('keydown', onEscClick);
window.addEventListener('wheel', preventWheelScroll, { passive: false });
return () => {
document.removeEventListener('keydown', onEscClick);
window.removeEventListener('wheel', preventWheelScroll);
};
});
const onCloseModalButtonClick = () => {
onCloseModal();
};
return (
<div className="overlay" ref={overlayRef}
onClick={(evt) => onOverlayClick(evt)}>
<div className="modal">
<CloseButton
ref={ref}
onClick={() => onCloseModalButtonClick()}
{...props}
/>
{children}
</div>
</div>
);
});
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/close-button/close-button.jsx [CloseButton full]
const CloseButton = forwardRef(({ props, ref }) => {
const {className, onClick, ...props} = props;
return (
<button className={`${className} close-button`}
onClick={(evt) => onClick(evt)}
tabIndex="0"
ref={ref}
{...props}
>Close</button>
);
});
And now i have a lot of errors just like: 1 - Cannot read properties of undefined (reading 'children') - Modal, 2 - ... className undefined in CloseButton etc.
2 popup Screen, components: Modal(reuse in 1 popup) - InfoSuccess- CloseButton(reuse in 1 popup)
I have only 1 interactive element - button (tabindex) and no more. Now i don't have any idea about 2 popup with focus-trap ((
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/success-modal/success-modal.jsx [SuccessModal full]
const SuccessModal = ({ className, onChangeVisibleSuccess }) => {
return (
<Modal onCloseModal={() => onChangeVisibleSuccess(false)}>
<InfoSuccess className={className} />
</Modal>
);
};
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/info-block/info-block.jsx [Infoblock full]
const InfoBlock = ({ className, title, desc, type }) => {
return (
<section className={`info-block ${className} info-block--${type}`}>
<h3 className="info-block__title">{title}</h3>
<p className="info-block__desc">{desc}</p>
</section>
);
};
const InfoSuccess = ({ className }) => (
<InfoBlock
title="Спасибо за обращение в наш банк."
desc="Наш менеджер скоро свяжется с вами по указанному номеру телефона."
type="center"
className={className}
/>
);
I know about 3 in 1 = 1 component and no problem in popup with Focus-Trap. But i want understand about my case, it's real to life or not and what best practice.
I am trying to create a button that add more input and remove input and when it remove the input it also clear all the data inside that input but the problem is when I remove that input but the data still stay. How can I fix that ?
Here is my code base:
const [inputs, setInputs] = useState(teamData.rules);
useEffect(() => {
setInputs(teamData.rules);
}, [teamData]);
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule-${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
{inputs.map((data, index) => (
<div className="agreement-form-grid" key={index}>
<button
className="agreement-remove-button"
onClick={() => handleRemoveClick(index)}
>
<Remove />
</button>
<input
type="text"
defaultValue={teamData.rules[index]}
name={`rule_${index}`}
placeholder={`Rule ${index + 1}`}
onChange={handleChange}
/>
</div>
))}
{inputs.length !== 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
Update question add handleChange function:
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
}
Define another function in your parent component to clear the data like below,
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState
}
});
}
In the child component, in the onClick of the remove button call this function as well
<button
className="agreement-remove-button"
onClick={() => { handleRemoveClick(index); clearInput(`rule_${index}`)}}
>
<Remove />
</button>
Before saving you can ignore empty inputs
Problem
I am trying to uplift an HOC from javascript to typescript. The HOC adds a confirmation dialog into the component that uses it, providing a prop showConfirmationDialog which when called, displays the dialog and runs the callback when hitting confirm.
The code compiles fine, but when I open the app in the browser, I get an error "Invalid hook call. Hooks can only be called inside of the body of a function component."
The code worked fine in javascript. I cannot understand the error, and I have followed all recommended steps but nothing is fixing it.
Code
ConfirmationDialog/index.tsx
type ExtraProps = {
showConfirmationDialog: (params: RequiredParameters) => void
}
type ConfirmationCallback = () => void
interface RequiredParameters {
dialogTitle: string,
dialogContent: string,
confirmationButtonText: string,
onConfirm: ConfirmationCallback
}
const WithConfirmationDialog = <P extends ExtraProps>(Component: React.ComponentType<P>) => {
const [open, setOpen] = useState(false)
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [confirmationButtonText, setConfirmationButtonText] = useState('')
const [onConfirm, setOnConfirm] = useState<ConfirmationCallback>()
const handleShow = (params: RequiredParameters) => {
setTitle(params.dialogTitle)
setContent(params.dialogContent)
setConfirmationButtonText(params.confirmationButtonText)
setOnConfirm(params.onConfirm)
setOpen(true)
}
const handleConfirm = () => {
if (onConfirm) {
onConfirm()
}
setOpen(false)
}
const handleClose = () => {
setOpen(false)
}
const ComponentWithConfirmationDialog = (props: P) => (
<>
<Dialog
open={open}
onClose={handleClose}
>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{content} </DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleConfirm} color="primary">
{confirmationButtonText}
</Button>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
</DialogActions>
</Dialog>
<Component {...props} showConfirmationDialog={handleShow} />
</>
)
return ComponentWithConfirmationDialog
}
export default WithConfirmationDialog
Sample of where the code is used after clicking a button in another component:
import withConfirmationDialog from '../ConfirmationDialog'
const MyComponent = (props) => {
const classes = useStyles();
const handleDeleteBooking = () => {
// ...make api calls and handle results...
};
// left out everything else for brevity
return (
<Fab // material-ui
className={classes.deleteButton}
aria-label="delete"
onClick={(e) => {
props.showConfirmationDialog({
dialogTitle: "Delete Booking",
dialogContent: "Are you sure you want to delete this booking?",
confirmationButtonText: "Delete",
onConfirm: handleDeleteBooking,
});
}}
>
<DeleteIcon /> // material-ui
</Fab>
);
};
export default withConfirmationDialog(MyComponent)
Additional Info
The main guide I used to build this can be found here.
When running npm start it compiles fine, and the error is never displayed in the terminal.
I only see in my browser the 'Invalid hook call' message, and a stack trace pointing to my first use of useState(false inside my HOC.
Any help would be greatly appreciated!
the issue here is your HOC is calling hooks outside of function component ComponentWithConfirmationDialog. all hooks must be called inside a component, not outside. your HOC function is not a Component itself.
in order to fix that you need to move all that is above ComponentWithConfirmationDialog to inside it like:
const WithConfirmationDialog = <P extends ExtraProps>(Component: React.ComponentType<P>) => {
const ComponentWithConfirmationDialog = (props: P) => {
const [open, setOpen] = useState(false)
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [confirmationButtonText, setConfirmationButtonText] = useState('')
const [onConfirm, setOnConfirm] = useState<ConfirmationCallback>()
const handleShow = (params: RequiredParameters) => {
setTitle(params.dialogTitle)
setContent(params.dialogContent)
setConfirmationButtonText(params.confirmationButtonText)
setOnConfirm(params.onConfirm)
setOpen(true)
}
const handleConfirm = () => {
if (onConfirm) {
onConfirm()
}
setOpen(false)
}
const handleClose = () => {
setOpen(false)
}
return (
<>
<Dialog
open={open}
onClose={handleClose}
>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{content} </DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleConfirm} color="primary">
{confirmationButtonText}
</Button>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
</DialogActions>
</Dialog>
<Component {...props} showConfirmationDialog={handleShow} />
</>
)
}
return ComponentWithConfirmationDialog
}
export default WithConfirmationDialog
Parent Component
function SourcePlate() {
const [isOpen, setIsOpen] = React.useState(false);
const handlePlateClick = () => {
setIsOpen(true);
}
const handleDialogClose = () => {
setIsOpen(false);
console.log(isOpen);
}
return (
<div onClick={handlePlateClick}>
<AutoCompleteDialog isOpen={isOpen} handleClose={handleDialogClose} title='Upload Plate'></AutoCompleteDialog>
</div>
)
}
Child Component
export const AutoCompleteDialog = ({
isOpen,
handleClose
}) => {
return (
<>
<Dialog
fullWidth
maxWidth='md'
open={isOpen}
onClose={handleClose}
>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
</>
)
}
My component's state doesn't update when using a callback function from child.
When I click the close button my dialog doesn't close and my isOpen state doesn't update to false. What can I fix/change?
I'm trying to read the id of a <Button /> component when it's clicked:
const FlagsDialog = (props) => {
const {
classes,
handleClose,
showFlagsDialog,
} = props;
const selectLang = (evt) => {
console.log('CLICKED', evt.target.id);
};
return (
<Dialog
open={showFlagsDialog}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<Button onClick={selectLang} id="en">English</Button>
<Button onClick={selectLang} id="es">Spanish</Button>
<Button onClick={selectLang} id="fr">French</Button>
<Button onClick={selectLang} id="de">German</Button>
</Dialog>
);
};
But when run, the click only returns the text CLICKED, and no value for the clicked button's id. What am I doing wrong?
I don't know what you're trying to do, but that code works.
I'm assiming Button and Dialog are your custom components (if not, you need to change them to button and dialog). I changed them to button and dialog in the code that I used and it works fine.
Here, check it out:
use a method instead of event for passing id as parameter.
const FlagsDialog = (props) => {
const {
classes,
handleClose,
showFlagsDialog,
} = props;
const selectLang = (id) => {
console.log(id);
//OR e.target.getAttribute("id")
};
return (
<Dialog
open={showFlagsDialog}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<Button onClick={() => this.selectLang(id)} id="en">English</Button>
<Button onClick={selectLang} id="en">English</Button>
</Dialog>
);
};
Alternatively you can use the fancy-pant ref function:
class App extends React.Component {
btns = [];
handleClick = index => console.log(this.btns[index].id);
render() {
return (
<div>
<button
ref={ref => (this.btns[0] = ref)}
onClick={() => this.handleClick(0)}
id="no1"
>
First
</button>
<button
ref={ref => (this.btns[1] = ref)}
onClick={() => this.handleClick(1)}
id="no2"
>
Second
</button>
</div>
);
}
}
Turns out this ref implementation is simpler than I expected.