'Invalid hooks call. Hooks can only be called inside of the body of a function component
Please have a look at the validate method inside the elfe-if
condition in the below code. I am unable to call the useLocation
method of react-router-dom
. I have seen questions online, one of which is similar to this: Invalid hook call. But the solution recommends changing from a class to a function. It might not be the solution for mine because I am using the instance of my class to find the values during my form validation.
export class Reset extends React.Component<IFormProps, IFormState> {
private currentPasswordProvided: boolean;
private passwordValidated: boolean;
private completeValidation: boolean;
private resetParams: ResetPassword;
constructor(props: IFormProps) {
super(props);
const errors: IErrors = {};
const values: IValues = {};
this.state = {
errors,
values
};
this.currentPasswordProvided=false;
this.passwordValidated= false;
this.completeValidation=false;
this.resetParams = {
adminID : '',
currentPassword: '',
newPassword: '',
}
}
private setValues = (values: IValues)=>{
this.setState({values: {...this.state.values, ...values}})
};
private validate = (fieldName: string, value: string): string => {
let newError: string = "";
if(fieldName!=='retypePassword'){
if (
this.props.fields[fieldName] &&
this.props.fields[fieldName].validation
){
newError = this.props.fields[fieldName].validation!.rule(
value,
fieldName,
this.props.fields[fieldName].validation!.args
);
if(fieldName=='currentPassword' && newError === "")
{
this.currentPasswordProvided = true;
}
else if(fieldName=='newPassword' && newError === "")
{
this.passwordValidated = true;
}
}
}
else if(fieldName === 'retypePassword'){
if (
this.props.fields[fieldName] &&
this.props.fields[fieldName].ComparePasswordValidation
){
let passwordValues : CompareValue = {
newPassword: this.state.values.newPassword
}
newError = this.props.fields[fieldName].ComparePasswordValidation!.rule(
value,
passwordValues,
this.props.fields[fieldName].ComparePasswordValidation!.args
);
if(this.currentPasswordProvided===true&&this.passwordValidated===true&&newError===""){
const params = useLocation(); //<------ unable to call this ------------
const adminID = params.pathname.substr(params.pathname.lastIndexOf('/')+1);
this.resetParams = {
adminID: adminID,
currentPassword: this.state.values.currentPassword,
newPassword: this.state.values.newPassword,
}
this.completeValidation = true;
}
else{
this.completeValidation = false;
}
}
}
this.state.errors[fieldName] = newError;
this.setState({
errors: { ...this.state.errors, [fieldName]: newError}
});
return newError;
}
private haveErrors(errors: IErrors) {
let haveError: boolean = true;
Object.keys(errors).map((key: string)=> {
if(errors[key].length > 0){
haveError = true;
}
});
return haveError;
}
private async handleSubmit (
e: React.FormEvent<HTMLFormElement>
): Promise<void> {
e.preventDefault();
try {
console.log(`These are the values----> ${this.state.values}`)
const resetParams : ResetPassword = {
adminID: 'adminID',
currentPassword : this.state.values.currentPassword,
newPassword: this.state.values.newPassword,
}
console.log(`These are the params being sent to the API ----> ${JSON.stringify(resetParams)}`)
/*
API call code goes here
*/
} catch (e) {
console.log(`Request failed: ${e}`);
// setShowAlert(true);
}
}
public render() {
const { submitSuccess, errors } = this.state;
const context: IFormContext = {
...this.state,
setValues: this.setValues,
validate: this.validate
};
// console.log(`This is the state ----> ${JSON.stringify(this.state.values)}`)
return (
<IonPage id="login-registration-page">
<IonHeader>
<IonToolbar color="primary">
<IonTitle>Reset Password</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<FormContext.Provider value={context}>
<form onSubmit={this.handleSubmit} className="login-registration-form ion-padding">
<div className="container">
{this.props.render()}
<div className="form-group">
<IonButton
type="submit"
//onClick={this.handleSubmit}
disabled={!this.completeValidation}
expand="block"
>Submit</IonButton>
</div>
{this.completeValidation === false &&
!this.haveErrors(errors) && (
<div className="alert alert-danger" role="alert">
Sorry, an unexpected error has occured
</div>
)}
</div>
</form>
</FormContext.Provider>
</IonContent>
</IonPage>
)
}
}
Solution 1:[1]
useLocation
is a custom react hook and can only be used inside of a function component i.e. a component that is defined as a javascript function. Your component is a class component.
To access the location
object provided by your router context in a class component you can use the withRouter
HOC:
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
The HOC injects the router context as props match
, location
and history
into your component.
Solution 2:[2]
Hooks are not supported in class components. If you can't easily rewrite this as a functional component, one option is to wrap your class component with a functional component and pass location
in as a prop. It looks like you're only using the params.pathname
so you could pass that in instead.
Something like this (component names are only a suggestion):
- Rename
Reset
toResetBody
- Create a new type for the props by extending.
interface IResetProps extends IFormProps {
adminID : string;
}
- Create a new component called
Reset
that looks like this:
function Reset(props : IFormProps){
const params = useLocation(); //<------ unable to call this ------------
const adminID = params.pathname.substr(params.pathname.lastIndexOf('/')+1);
return <ResetBody {...props} adminID={adminID} />
}
Solution 3:[3]
If you are using a class, and you don't want to replace your react class component to function component, you need to use the withRouter
, in router versions before v6.
In React-Router-Dom V6-> this class is not exists any more.
See: What happened to withRouter? I need it!
This question usually stems from the fact that you're using React class components, which don't support hooks. In React Router v6, we fully embraced hooks and use them to share all the router's internal state. But that doesn't mean you can't use the router. Assuming you can actually use hooks (you're on React 16.8+), you just need a wrapper.
So, you'll need to create your own wrapper, as shown in the docs.
Here is my implementation, a little more easy-to-use than doc example:
import React from 'react';
import {useLocation, useNavigate, useParams} from 'react-router-dom';
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return <Component {...props} {...{location, navigate, params}} />;
}
return ComponentWithRouterProp;
}
export default withRouter;
Use (when exporting your component):
export default withRouter(Reset);
And, in your component, you can use:
this.props.location.pathname
this.props.params.paramName
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Frank Riccobono |
Solution 3 |