reactjs - React högre ordningens komponent fungerar inte i typescript
Problem
Jag försöker uppgradera en HOC från JavaScript till TypeScript. HOC:en lägger till en bekräftelsedialog i komponenten som använder den och tillhandahåller en prop showConfirmationDialog som, när den anropas, visar dialogen och kör callbacken när man trycker på bekräfta.
Koden kompileras utan problem, men när jag öppnar appen i webbläsaren får jag ett fel "Ogiltigt hook-anrop. Hooks kan endast anropas inuti kroppen av en funktionskomponent."
Koden fungerade bra i JavaScript. Jag kan inte förstå felet, och jag har följt alla rekommenderade steg men inget rättar till det.
Kod
ConfirmationDialog/index.tsxtype 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
Exempel på var koden används efter att ha klickat på en knapp i en annan komponent:
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)
Ytterligare information
Den huvudsakliga guiden jag använde för att bygga detta kan hittas här. När jag kör npm start kompileras det utan problem, och felet visas aldrig i terminalen. Jag ser endast i min webbläsare meddelandet 'Invalid hook call' och en stackspårning som pekar på min första användning av useState(false inuti min HOC.
All hjälp skulle uppskattas mycket!
Svar #1
problemet här är att din HOC anropar hooks utanför funktionskomponenten ComponentWithConfirmationDialog. alla hooks måste anropas inuti en komponent, inte utanför. din HOC-funktion är inte en komponent i sig.
för att åtgärda det behöver du flytta allt som är ovanför ComponentWithConfirmationDialog inuti det som:
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
