0

I'm trying to get the hang of react/redux with a login page to expand my knowledge. I am having some issues with the following error:

Invalid hook call. Hooks can only be called inside of the body of a function component.

I know this has been posted on here a lot but none of the answers are sticking for me. i can get the store to work just fine in other parts of of app just having some trouble with the logic here. any help is appreciated. My login page is this:

import React, { useState } from "react";
import { Grid, CircularProgress, Typography, Button, Tabs, Tab, TextField, Fade } from "@material-ui/core";
import { withRouter } from "react-router-dom";
import useStyles from "./styles";
import logo from "./logo.svg";
import { LoginUser } from "../../comps/Userauth";

function Login(props) {
  var classes = useStyles();
  var [isLoading, setIsLoading] = useState(false);
  var [error, setError] = useState(null);
  var [activeTabId, setActiveTabId] = useState(0);
  var [loginValue, setLoginValue] = useState("");
  var [passwordValue, setPasswordValue] = useState("");
  return (
    <Grid container className={classes.container}>
      <div className={classes.logotypeContainer} style={{zIndex: '1'}} >
        <img src={logo} alt="logo" className={classes.logotypeImage} />
        <Typography className={classes.logotypeText}>test app</Typography>
      </div>
      <div className={classes.formContainer}>
        <div className={classes.form}>
          <Tabs
            value={activeTabId}
            onChange={(e, id) => setActiveTabId(id)}
            indicatorColor="primary"
            textColor="primary"
            centered
          >
            <Tab label="Login" classes={{ root: classes.tab }} />
          </Tabs>
          {activeTabId === 0 && (
            <React.Fragment>
              <Fade in={error}>
                <Typography color="secondary" className={classes.errorMessage}>
                  Please try again.

                </Typography>
              </Fade>
              <TextField
                id="username"
                InputProps={{
                  classes: {
                    underline: classes.textFieldUnderline,
                    input: classes.textField,
                  },
                }}
                value={loginValue}
                onChange={e => setLoginValue(e.target.value)}
                margin="normal"
                placeholder="Username"
                type="text"
                fullWidth
              />
              <TextField
                id="password"
                InputProps={{
                  classes: {
                    underline: classes.textFieldUnderline,
                    input: classes.textField,
                  },
                }}
                value={passwordValue}
                onChange={e => setPasswordValue(e.target.value)}
                margin="normal"
                placeholder="Password"
                type="password"
                fullWidth
              />
              <div className={classes.formButtons}>
                {isLoading ? (
                  <CircularProgress size={26} className={classes.loginLoader} />
                ) : (
                  <Button
                    disabled={
                      loginValue.length === 0 || passwordValue.length === 0
                    }
                    onClick={() =>
                      LoginUser(
                        loginValue,
                        passwordValue,
                        props.history,
                        setIsLoading,
                        setError,
                      )
                    }
                    variant="contained"
                    color="primary"
                    size="large"
                  >
                    Login
                  </Button>
                )}
              </div>
            </React.Fragment>
          )}
        </div>
      </div>
    </Grid>
  );
}
export default withRouter(Login);

And the userauth req:

import React from "react";
import axios from "axios";
import {useSelector, useDispatch} from 'react-redux'
import allActions from '../actions'
var jwtDecode = require('jwt-decode');

function LoginUser(login, password, history, setIsLoading, setError) {
    const currentUser = useSelector(state => state.currentUser)
    const dispatch = useDispatch()
  try {
  setError(false);
  setIsLoading(true);
  axios.post('/login', {username: login, password: password}, {
  }).catch(function (error) {
    if (error.response) {
        setError(true);
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
        setError(true);
      console.log(error.request);
    } else {
        setError(true);
      console.log('Error', error.message);
    }
  }).then(function(response) {
    if (response.status == '200') {
      setTimeout(() => {
        setError(null)
        setIsLoading(false)
        let token1 = jwtDecode(response.data.token);
        dispatch(allActions.userActions.setUser(token1.username))
        history.push('/app/dashboard')
      }, 2000);
    } else {
      setError(true);
      setIsLoading(false);
    }  
  })
} catch (error) {
  setError(true);
  setIsLoading(false);
}
}
function signOut(dispatch, history) {
  dispatch(allActions.userActions.logOut())
  history.push("/login");
}
export { LoginUser, signOut };

1 Answer 1

1

LoginUser is not a React component, it's just a function that handles an event. And, as the message states, you can't use hooks unless react is rendering a component.

You'll have to either pass in everything your login function needs as arguments, or refactor things.

One way to refactor this would be to create a custom hook that provides this login function to you.

export default useLoginHandler(history, setIsLoading, setError) {
  const currentUser = useSelector(state => state.currentUser)
  const dispatch = useDispatch()

  return {
    onLogin(login, password) {
      doStuff().then(() => {
        // use values from above hooks
        dispatch(yourActionMaker(whatever))
      })
    },
    onLogout() {
      dispatch(allActions.userActions.logOut())
      history.push("/login");
    },
  }
}

Now in your component, use that like any other hook:

function Login(props) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const {onLogin, onLogOut} = useLoginHandler(props.history, setIsLoading, setError)

  // other hooks...

  return <React.Fragment>
    {/* other rendering... */}
    <div onClick={() => onLogin(loginValue, passwordValue)}>
      login
    </div>
  </React.Fragment>
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you sir. I did not know you could return other functions in a functional component. For some reason I figured you could only return JSX or a variable. Appreciate it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.