gitdagray/react_login_hooks

setAuth is not a function, setJWT under useLocalStorage not a function

Closed this issue · 3 comments

ehgp commented

Dependencies

Win 11
NodeJS 16.16.0 (Currently using 64-bit executable)

package.json

"dependencies": {
    "@emotion/cache": "11.7.1",
    "@emotion/react": "11.7.1",
    "@emotion/styled": "11.6.0",
    "@fvilers/disable-react-devtools": "^1.3.0",
    "@mui/icons-material": "5.4.1",
    "@mui/material": "5.4.1",
    "@mui/styled-engine": "5.4.1",
    "@testing-library/jest-dom": "5.16.2",
    "@testing-library/react": "12.1.2",
    "@testing-library/user-event": "13.5.0",
    "axios": "^0.24.0",
    "chart.js": "3.4.1",
    "chroma-js": "2.4.2",
    "dotenv-webpack": "^1.7.0",
    "history": "^5.3.0",
    "prop-types": "15.8.1",
    "react": "^17.0.2",
    "react-chartjs-2": "3.0.4",
    "react-dom": "^17.0.2",
    "react-github-btn": "1.2.1",
    "react-router-dom": "^6.2.1",
    "react-scripts": "5.0.0",
    "react-spinners": "^0.10.4",
    "react-table": "7.7.0",
    "stylis": "4.0.13",
    "stylis-plugin-rtl": "2.0.2",
    "web-vitals": "2.1.4",
    "yup": "0.32.11"
  },
  "scripts": {
    "start": "PORT=4000 react-scripts start",
    "https-start": "HTTPS=true SSL_CRT_FILE=../certs/server.crt SSL_KEY_FILE=../certs/server.key PORT=4000 react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "dev-pc": "set PORT=4000 && react-scripts start",
    "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm start"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "prettier": "2.5.1"
  }
}

jsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "*": ["public/src/*"]
    }
  }
}

.prettierrc.json

{
  "printWidth": 100,
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": false,
  "endOfLine": "auto"
}

AuthContext.jsx

import React, { createContext, useContext, useState } from "react";

const AuthContext = createContext({});

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({
    token: "",
  });
  return <AuthContext.Provider value={{ auth, setAuth }}>{children}</AuthContext.Provider>;
};

const useAuth = () => {
  const context = useContext(AuthContext);

  if (!context) {
    throw new Error("useAuth must be used within AuthProvider");
  }
  return context;
};

export { AuthProvider, useAuth };

I had also used the original useAuth provided by your tutorial and I got the same error.

useLocalStorage.js

import { useState, useEffect } from "react";

const getLocalValue = (key, initValue) => {
  // SSR Next.js
  if (typeof window === "undefined") return initValue;

  // if a value is already store
  const localValue = JSON.parse(localStorage.getItem(key));
  if (localValue) return localValue;

  // return result of a function
  if (initValue instanceof Function) return initValue();

  return initValue;
};

const useLocalStorage = (key, initValue) => {
  const [value, setValue] = useState(() => {
    return getLocalValue(key, initValue);
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};

export default useLocalStorage;

The Error

setAuth is not a function / can uncomment useLocalStorage lines to recreate setJWT error

useLogin.js

import API from "../api/axios";
import { useAuth } from "../context/AuthContext";
//  import useLocalStorage from "../hooks/useLocalStorage";

const useLogin = () => {
  const { setAuth } = useAuth();
  // const [setJWT] = useLocalStorage("jwt", false);

  const loginReq = async ({ email, password }) => {
    const data = { email, password };
    try {
      let res = await API.post(
        `api/signin`,
        data
        //   ,
        //   {
        //     headers: { 'Content-Type': 'application/json' },
        //     withCredentials: true
        // }
      );
      const accessToken = res.data;
      // setJWT(accessToken);
      // this works
      localStorage.setItem("jwt", JSON.stringify(accessToken));
      // cannot import setAuth state from authcontext for some reason
      //   console.log(setAuth.setAuth());
      setAuth({ accessToken });
      return accessToken;
    } catch (error) {
      console.log(error);
    }
  };

  return loginReq;
};

export default useLogin;

authentication/sign-in/index.js

import { useState } from "react";

// react-router-dom components
import { Link } from "react-router-dom";

// @mui material components
import Card from "@mui/material/Card";
import Switch from "@mui/material/Switch";
// import Grid from "@mui/material/Grid";
// import MuiLink from "@mui/material/Link";

// @mui icons
// import FacebookIcon from "@mui/icons-material/Facebook";
// import GitHubIcon from "@mui/icons-material/GitHub";
// import GoogleIcon from "@mui/icons-material/Google";

import useLogin from "../../../hooks/useLogin";

// components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import MDInput from "components/MDInput";
import MDButton from "components/MDButton";

// Authentication layout components
import BasicLayout from "layouts/authentication/components/BasicLayout";

// Images
import bgImage from "assets/images/bg-sign-in-basic.jpeg";

function Basic() {
  const [signIn, setSignIn] = useState({
    email: "",
    password: "",
    error: false,
    loading: true,
  });
  const [rememberMe, setRememberMe] = useState(false);
  const handleSetRememberMe = () => setRememberMe(!rememberMe);
  const loginReq = useLogin();
  // const handleSetSignIn = () => setSignIn(!signIn);

  // const { data: layoutData, dispatch: layoutDispatch } = useContext(LayoutContext);

  const alert = (msg) => <div className="text-xs text-red-500">{msg}</div>;

  const formSubmit = async () => {
    setSignIn({ ...signIn, loading: true });
    try {
      let res = await loginReq({
        email: signIn.email,
        password: signIn.password,
      });
      if (res.error) {
        setSignIn({
          ...signIn,
          loading: false,
          error: res.error,
          password: "",
        });
      } else if (res.accessToken) {
        setSignIn({ email: "", password: "", loading: false, error: false });
        window.location.href = "/";
      }
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <BasicLayout image={bgImage}>
      <Card>
        <MDBox
          variant="gradient"
          bgColor="info"
          borderRadius="lg"
          coloredShadow="info"
          mx={2}
          mt={-3}
          p={2}
          mb={1}
          textAlign="center"
        >
          <MDTypography variant="h4" fontWeight="medium" color="white" mt={1}>
            Sign in
          </MDTypography>
          {/* <Grid container spacing={3} justifyContent="center" sx={{ mt: 1, mb: 2 }}>
            <Grid item xs={2}>
              <MDTypography component={MuiLink} href="#" variant="body1" color="white">
                <FacebookIcon color="inherit" />
              </MDTypography>
            </Grid>
            <Grid item xs={2}>
              <MDTypography component={MuiLink} href="#" variant="body1" color="white">
                <GitHubIcon color="inherit" />
              </MDTypography>
            </Grid>
            <Grid item xs={2}>
              <MDTypography component={MuiLink} href="#" variant="body1" color="white">
                <GoogleIcon color="inherit" />
              </MDTypography>
            </Grid>
          </Grid> */}
        </MDBox>
        <MDBox pt={4} pb={3} px={3}>
          <MDBox component="form" role="form">
            <MDBox mb={2}>
              <MDInput
                type="email"
                label="Email"
                fullWidth
                onChange={(e) => {
                  setSignIn({ ...signIn, email: e.target.value, error: false });
                  // layoutDispatch({ type: "loginSignupError", payload: false });
                }}
                value={signIn.email}
                id="email"
                className={`${
                  !signIn.error ? "" : "border-red-500"
                } px-4 py-2 focus:outline-none border`}
              />
              {!signIn.error ? "" : alert(signIn.error)}
            </MDBox>
            <MDBox mb={2}>
              <MDInput
                type="password"
                label="Password"
                fullWidth
                onChange={(e) => {
                  setSignIn({ ...signIn, password: e.target.value, error: false });
                  // layoutDispatch({ type: "loginSignupError", payload: false });
                }}
                value={signIn.password}
                id="password"
                className={`${
                  !signIn.error ? "" : "border-red-500"
                } px-4 py-2 focus:outline-none border`}
              />
              {!signIn.error ? "" : alert(signIn.error)}
            </MDBox>
            <MDBox display="flex" alignItems="center" ml={-1}>
              <Switch checked={rememberMe} onChange={handleSetRememberMe} />
              <MDTypography
                variant="button"
                fontWeight="regular"
                color="text"
                onClick={handleSetRememberMe}
                sx={{ cursor: "pointer", userSelect: "none", ml: -1 }}
              >
                &nbsp;&nbsp;Remember me
              </MDTypography>
            </MDBox>
            <MDBox mt={4} mb={1}>
              <MDButton variant="gradient" color="info" fullWidth onClick={(e) => formSubmit()}>
                sign in
              </MDButton>
            </MDBox>
            <MDBox mt={3} mb={1} textAlign="center">
              <MDTypography variant="button" color="text">
                Don&apos;t have an account?{" "}
                <MDTypography
                  component={Link}
                  to="/authentication/sign-up"
                  variant="button"
                  color="info"
                  fontWeight="medium"
                  textGradient
                >
                  Sign up
                </MDTypography>
              </MDTypography>
            </MDBox>
            <MDBox mt={1} mb={1} textAlign="center">
              <MDTypography variant="button" color="text">
                <MDTypography
                  component={Link}
                  to="/authentication/reset-password"
                  variant="button"
                  color="info"
                  fontWeight="medium"
                  textGradient
                >
                  Forgot Password?
                </MDTypography>
              </MDTypography>
            </MDBox>
          </MDBox>
        </MDBox>
      </Card>
    </BasicLayout>
  );
}

export default Basic;

Error

TypeError: setJWT is not a function
    at loginReq (useLogin.js:22:1)
    at async formSubmit (index.js:65:1)
index.js:81 TypeError: Cannot read properties of undefined (reading 'error')
    at formSubmit (index.js:69:1)
TypeError: setAuth is not a function
    at loginReq (useLogin.js:27:1)
    at async formSubmit (index.js:65:1)
index.js:81 TypeError: Cannot read properties of undefined (reading 'error')
    at formSubmit (index.js:69:1)

I am not an expert on calling functions or objects across files but I cannot quite put my finger on this one. If I use raw LocalStorage everything works fine.

ehgp commented

I got setJWT to work by introducing both parameters.

const [jwt, setJWT] = useLocalStorage("jwt", "");

I still cannot get useAuth to work however.

If you have questions about a tutorial, may I suggest my Discord channel: https://discord.gg/neKghyefqh

You have posted a lot of code here that is not from my tutorial.

What I don't see is your index.js that imports the App.js. In that file, you also need to import your context. Look at mine in this repository. From there, you need the AuthProvider like I created to provide the context to the rest of your app.

This is not an issue with the repository provided.

ehgp commented

Thanks for the invite! I did not post my index.js and App.js! It must be that for sure!

App.js

import { useState, useEffect, useMemo } from "react";

// RTL plugins
import rtlPlugin from "stylis-plugin-rtl";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";

// @mui material components
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import Icon from "@mui/material/Icon";

// react-router components
import { Routes, Route, Navigate, useLocation } from "react-router-dom";

// omponents
import MDBox from "./components/MDBox";

// example components
import Sidenav from "./examples/Sidenav";
import Configurator from "./examples/Configurator";

// themes
import theme from "./assets/theme";
import themeRTL from "./assets/theme/theme-rtl";

// Dark Mode themes
import themeDark from "./assets/theme-dark";
import themeDarkRTL from "./assets/theme-dark/theme-rtl";

// Client Portal routes
import { user_routes, admin_routes, public_routes } from "./routes/routes";
import useLocalStorage from "hooks/useLocalStorage";

// contexts
import { useMaterialUIController, setMiniSidenav, setOpenConfigurator } from "./context";

// Images
import brandWhite from "./assets/images/Badge_HG_States.png";
import brandDark from "./assets/images/Badge_black_transparent.png";

export default function App() {
  const [controller, dispatch] = useMaterialUIController();
  const {
    miniSidenav,
    direction,
    layout,
    openConfigurator,
    sidenavColor,
    transparentSidenav,
    whiteSidenav,
    darkMode,
  } = controller;
  const [onMouseEnter, setOnMouseEnter] = useState(false);
  const [rtlCache, setRtlCache] = useState(null);
  const { pathname } = useLocation();
  const ROLES = ["user", "admin", "owner"];
  const [jwt, setJWT] = useLocalStorage("jwt", "");

  // Cache for the rtl
  useMemo(() => {
    const cacheRtl = createCache({
      key: "rtl",
      stylisPlugins: [rtlPlugin],
    });

    setRtlCache(cacheRtl);
  }, []);

  // Open sidenav when mouse enter on mini sidenav
  const handleOnMouseEnter = () => {
    if (miniSidenav && !onMouseEnter) {
      setMiniSidenav(dispatch, false);
      setOnMouseEnter(true);
    }
  };

  // Close sidenav when mouse leave mini sidenav
  const handleOnMouseLeave = () => {
    if (onMouseEnter) {
      setMiniSidenav(dispatch, true);
      setOnMouseEnter(false);
    }
  };

  // Change the openConfigurator state
  const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);

  // Setting the dir attribute for the body element
  useEffect(() => {
    document.body.setAttribute("dir", direction);
  }, [direction]);

  // Setting page scroll to 0 when changing the route
  useEffect(() => {
    document.documentElement.scrollTop = 0;
    document.scrollingElement.scrollTop = 0;
  }, [pathname]);

  const getRoutes = (allRoutes) =>
    allRoutes.map((route) => {
      if (route.user) {
        return getRoutes(route.user);
      }

      if (route.admin) {
        return getRoutes(route.admin);
      }

      if (route.public) {
        return getRoutes(route.public);
      }

      if (route.route) {
        return <Route exact path={route.route} element={route.component} key={route.key} />;
      }

      return null;
    });

  const configsButton = (
    <MDBox
      display="flex"
      justifyContent="center"
      alignItems="center"
      width="3.25rem"
      height="3.25rem"
      bgColor="white"
      shadow="sm"
      borderRadius="50%"
      position="fixed"
      right="2rem"
      bottom="2rem"
      zIndex={99}
      color="dark"
      sx={{ cursor: "pointer" }}
      onClick={handleConfiguratorOpen}
    >
      <Icon fontSize="small" color="inherit">
        settings
      </Icon>
    </MDBox>
  );

  return direction === "rtl" ? (
    <CacheProvider value={rtlCache}>
      <ThemeProvider theme={darkMode ? themeDarkRTL : themeRTL}>
        <CssBaseline />
        <Routes>
          {getRoutes(routes)}
          <Route path="*" element={<Navigate to="/dashboard" />} />
        </Routes>
        {layout === "dashboard" && (
          <>
            <Sidenav
              color={sidenavColor}
              brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
              brandName=" "
              routes={routes}
              onMouseEnter={handleOnMouseEnter}
              onMouseLeave={handleOnMouseLeave}
            />
            <Configurator />
            {configsButton}
          </>
        )}
        {layout === "vr" && <Configurator />}
      </ThemeProvider>
    </CacheProvider>
  ) : (
    <ThemeProvider theme={darkMode ? themeDark : theme}>
      <CssBaseline />
      <Routes>
        {jwt
          ? jwt?.user_role === "user"
            ? getRoutes(user_routes)
            : getRoutes(admin_routes)
          : getRoutes(public_routes)}
        {/* catch all */}
        <Route path="*" element={<Navigate to="/404" />} />
      </Routes>
      {layout === "dashboard" && (
        <>
          <Sidenav
            color={sidenavColor}
            brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
            brandName=" "
            routes={user_routes}
            onMouseEnter={handleOnMouseEnter}
            onMouseLeave={handleOnMouseLeave}
          />
          <Configurator />
          {configsButton}
        </>
      )}
      {layout === "vr" && <Configurator />}
    </ThemeProvider>
  );
}

index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";


import { MaterialUIControllerProvider } from "./context";

ReactDOM.render(
  <BrowserRouter>
    <MaterialUIControllerProvider>
      <App />
    </MaterialUIControllerProvider>
  </BrowserRouter>,
  document.getElementById("root")
);