Material-UI passing data between components - reactjs

This might look like a similar question but I'm unable to get the answer of it on Stackoverflow.
I have two components - A.js and B.js
A.js
(The below code is inside return() )
<Link to={{
pathname: `${simulationId}/edit/${ruleName}`,
search: createButtonQuery,
previewFlag = true,
}}>
<IconButton color="primary" size="small">
<PageviewOutlinedIcon/>
</IconButton>
</Link>
B.js
(The below code is inside return() )
<DialogTitle>{name ? 'Print true' : 'Print false'}</DialogTitle>
Issue:
I want to test for previewFlag inside the B.js. I want to check when the previewFlag is true and name exists, 'Print true' should be returned by the <DialogTitle>
I don't know how to use previewFlag inside B.js though.
Kindly note that <PageViewOutlineIcon> is part of material-ui here.
The both components aren't being imported by each other.

To use a context, there are a few things you have to do:
1: Create a context file (I usually put mine in a contexts folder)
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } from '../firebase'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
This is the file that can hold whatever you want to check for
Then, you need to put it in your App/_app (depending on how you're using react) as in this case:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Then, in any component that you want access to those contexts, you can invoke it to use the values (or helper functions):
import { Alert, Grid, TextField, Button, makeStyles, Typography } from '#material-ui/core'
import { useAuth } from '../contexts/AuthContext'
import { useState } from 'react'
const theme = makeStyles({
form: {
width: '100vw',
maxWidth: '400px',
margin: 'auto',
padding: '1rem',
'& > div': {
paddingBottom: '1rem'
}
}
})
export default function LoginForm() {
const { login } = useAuth();
const styles = theme()
const [state, setState] = useState({
email: "",
password: ""
})
const [error, setError] = useState()
const { googleLogin } = useAuth()
function handleForm(e) {
setState({
...state,
[e.target.name]: e.target.value
})
}
async function handleLogin() {
await login(state.email, state.password)
.catch(err => {
console.log(err)
setError(JSON.stringify(err))
})
}
return(
<Grid container className={styles.form} direction="column" alignContent="stretch" justify="center">
<Grid item>
<Typography variant="h3">Login</Typography>
</Grid>
<Grid item>
{error && <Alert severity="error" variant="filled" >{error}</Alert>}
<TextField fullWidth name="email" label="Email" variant="outlined" onChange={handleForm}/>
</Grid>
<Grid item>
<TextField fullWidth name="password" type="password" label="Password" variant="outlined" onChange={handleForm} />
</Grid>
<Grid item>
<Button variant="contained" color="primary"fullWidth onClick={handleLogin}>Log In</Button>
</Grid>
</Grid>
)
}
notice how I import the useAuth instance (which is just a context I've named that. You can name it whatever) in my loginForm component, and then I can destructure out the values that I've exposed: const { login } = useAuth() Something like this: const { value, helperFunction } = useAuth() - assuming you're exporting a value and a function helperFunction from your context

Related

in NEXTJS Warning: Expected server HTML to contain a matching <div> in <div>

I am using nextjs and mui. I am facing a warning when rendering pages. Here is my code. Please help to solve the issue!!!
import "../styles/globals.scss";
import { AppProps } from "next/app";
import useGetAuthentication from "../hooks/useGetAuthentication";
import States from "../interfaces/states";
import STATUS from "../constants/status";
import { CssBaseline } from "#mui/material";
import { ThemeProvider } from "#mui/material/styles";
import theme from "../styles/theme";
import Layout from "../layouts/Layout";
import Head from "next/head";
import * as React from "react";
import Login from "../components/Login";
import { Box } from "#mui/material";
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const checkStatusCode = (statusCode: number): boolean => {
return statusCode === STATUS.NOT_FOUND || statusCode === STATUS.INTERNAL_SERVER_ERROR;
};
function App({ Component, pageProps }: AppProps) {
const { states } = pageProps;
const { statusCode } = pageProps;
const { isAuthorized } = useGetAuthentication(states as States);
console.log("App -> Component", Component);
console.log("App -> pageProps", pageProps);
console.log("App -> states", states);
console.log("App -> statusCode", statusCode);
const drawerWidth: number = 240;
if (!checkStatusCode(statusCode) && !isAuthorized)
return (
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { lg: "230px", sm: `calc(100% - ${drawerWidth}px)` }
}}
>
<Login />
</Box>
);
return (
<>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</>
);
}
export default App;
Login component is below
import React, { useEffect } from "react";
import authenticationStore from "../../stores/persistences/authenticationStore";
import TestHttp from "../../httpModules/testHttp";
import STATUS from "../../constants/status";
import RequestSignIn from "../../interfaces/test/requestSignIn";
import styles from "./login.module.scss";
import { Alert, FormControlLabel, Grid, Paper, TextField, Typography, Stack, Button, Checkbox } from "#mui/material";
import Image from "next/image";
import useLoginInputs from "../../hooks/useLoginInputs";
import LocalStorageHandler from "../../utils/localStorageHandler";
import RememberId from "../../interfaces/rememberId";
import ERROR_MESSAGE from "../../constants/errorMessage";
import LOGIN_INFO from "../../constants/loginInfo";
const Login: React.FC = () => {
const localStorageHandler = new LocalStorageHandler<RememberId>();
const authorize = authenticationStore((state) => state.authorize);
const testHttp = new TestHttp();
const { inputs, setInputs, isRememberChecked, isError, setIsError, isIdEmpty, setIsIdEmpty, isPasswordEmpty, setIsPasswordEmpty, inputsHandler, checkboxHandler } = useLoginInputs([
"id",
"password"
]);
const { id, password } = inputs;
const onLoginHandler = async (): Promise<void> => {
if (isIdEmpty) {
return setIsError(ERROR_MESSAGE.ID_EMPTY);
}
if (isPasswordEmpty) {
return setIsError(ERROR_MESSAGE.PASSWORD_EMPTY);
}
const signInInfo: RequestSignIn = { id, password };
const { statusCode, jsonResult } = await testHttp.signIn(false, signInInfo);
/*
* 인증 실패 (아이디, 비밀번호 일치 하지 않는 경우 등) 발생 시 코드 작성
*/
if (statusCode !== STATUS.OK) {
setIsError(true);
return;
}
const { userInfo, tokenInfo } = jsonResult;
authorize(statusCode, userInfo, tokenInfo);
if (!isRememberChecked) return localStorageHandler.removeLocalStorageData(LOGIN_INFO.REMEMBER_ID);
localStorageHandler.setLocalStorageData("rememberId", {
id
});
};
useEffect(() => {
console.log("하이");
setIsIdEmpty(id.length <= 0);
setIsPasswordEmpty(password.length <= 0);
setIsError(null);
}, [id, password]);
console.log("id", id);
console.log("password", password);
return (
<Grid>
<Paper elevation={10} className={styles.container}>
<Grid align={"center"}>
<div className={styles.logo}>
<Image src={"/images/logo.svg"} width={"200px"} height={"80px"} alt={"logo"} />
<Typography variant={"h6"}>관리자</Typography>
</div>
</Grid>
<Stack spacing={1} justifyContent={"center"} alignItems={"center"} className={styles["login-container"]}>
<TextField name={"id"} placeholder={"아이디를 입력해주세요."} required value={id} type={"text"} className={styles["login-input"]} onChange={inputsHandler} />
<TextField name={"password"} placeholder={"비밀번호를 입력하세요."} required value={password} type={"password"} className={styles["login-input"]} onChange={inputsHandler} />
</Stack>
<Stack>
<FormControlLabel control={<Checkbox checked={isRememberChecked} />} label={"아이디 저장"} className={styles.checkbox} onChange={checkboxHandler} />
</Stack>
<Stack justifyContent={"center"} alignItems={"center"}>
<Button type={"submit"} color={"primary"} variant={"contained"} className={styles["login-button"]} size={"large"} onClick={onLoginHandler}>
로그인
</Button>
</Stack>
<Stack justifyContent={"center"} alignItems={"center"} className={styles["error-message"]}>
<div>
{isError && (
<Alert severity={"error"}>
<strong>{isError}</strong>
</Alert>
)}
</div>
</Stack>
</Paper>
</Grid>
);
};
export default Login;
the warning is
Warning: Expected server HTML to contain a matching <div> in <div>.
at div
at eval (webpack-internal:///./node_modules/#emotion/react/dist/emotion-element-cbed451f.browser.esm.js:57:66)
at Box (webpack-internal:///./node_modules/#mui/system/esm/createBox.js:36:72)
at Layout (webpack-internal:///./layouts/Layout/index.tsx:16:26)
at InnerThemeProvider (webpack-internal:///./node_modules/#mui/system/esm/ThemeProvider/ThemeProvider.js:21:70)
at ThemeProvider (webpack-internal:///./node_modules/#mui/private-theming/ThemeProvider/ThemeProvider.js:47:5)
at ThemeProvider (webpack-internal:///./node_modules/#mui/system/esm/ThemeProvider/ThemeProvider.js:41:5)
at App (webpack-internal:///./pages/_app.tsx:61:27)
at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:20638)
at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:23179)
at Container (webpack-internal:///./node_modules/next/dist/client/index.js:323:9)
at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:820:26)
at Root (webpack-internal:///./node_modules/next/dist/client/index.js:944:27)
window.console.error # next-dev.js?3515:25
Case 1
Most likely a Server<>Client out of date issue.
Fix
If you are using the development server > Restart it.
If you are getting this production > Rebuild + Restart.
Case 2
The components you are using render differently (due to bad coding) on the Server (SSR) vs Client (CSR). This can be silenced by adding suppressHydrationWarning={true} to the offending component.
Case 3
Another case I've seen is that someone has set dangerouslySetInnerHtml with invalid HTML. The fix is to correct the HTML OR silence it as we did in case 2.
In my case I tried to run localStorage getItem method outside useEffect hook.
So probably you can place your async code inside that hook.
In progress value update from use hook can also causing this problem. To prevent it, just copy the variable value from hook to state from useEffect hook. Here is the example
import { useAccount } from "wagmi";
...
const { address, isConnected, isConnecting } = useAccount();
// the value of these variable above are changed dynamically
const [connectionStat, setConnectionStat] = useState();
const [addr, setAddr] = useState();
// copy the value to state here
useEffect(() => {
setConnectionStat(isConnected);
setAddr(address);
}, [address, isConnected])
// then now we can display the value properly
return (
<div>
<p>Connection status : {connectionStat}</p>
<p>Connected to : {addr}</p>
....
</div>
);
Explanation: passing value directly from hook to JSX/view may causing inconsistent value inside JSX/view so the the page render can not be consistent as well and there will be different value between value in SSR and client.
Hope it helps.

API data not printing but successes with console.log

I'm trying to learn about APIs and trying to code a REACT app to go along with it. I am sure the issue is a minor one, but I can't seem to crack it.
The relevant code is pasted below, the API is fetched in index.js.
The contents of the API is printed to the console without issue but I can not seem to get it right when going through my list and event details.
I am new to coding so I would appreciate any feedback given.
App.js
import React, { useState, useEffect } from "react";
import { CssBaseline, Grid } from "#material-ui/core";
import { getEventsData } from "./api";
import Header from "./components/Header/Header";
import List from "./components/List/List";
import EventDetails from "./components/EventDetails/EventDetails";
const App = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
getEventsData()
.then((data) => {
console.log(data);
console.log(Array.isArray(data))
setEvents(data);
})
}, []);
return (
<>
<CssBaseline />
<Header />
<List EventDetails={EventDetails} />
</>
)
}
export default App;
index.js
import axios from "axios";
const URL = 'https://api-football-v1.p.rapidapi.com/v3/fixtures'
const options = {
params: {date: '2022-02-12', league: '39', season: '2021'},
headers: {
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'x-rapidapi-key': xxxXXXxxxXXXxxx'
}
};
export const getEventsData = async () => {
try {
const { data } = await axios.get(URL, options);
// Kan det ha något med options att göra? https://stackoverflow.com/questions/68367352/multiple-url-variable-async-await-axios
return data;
} catch (error) {
}
};
List.jsx
import React, { useState } from "react";
import { CircularProgress, Grid, Typography, InputLabel, MenuItem, FormControl, Select, ButtonGroup, Button } from "#material-ui/core";
import EventDetails from "../EventDetails/EventDetails"
import useStyles from "./styles"
const List = ({ events }) => {
const classes = useStyles();
const [type, setType] = useState("premierleague");
return (
<div className={classes.container}>
<FormControl className={classes.formControl}>
<InputLabel>Sport</InputLabel>
<Select value={type} onChange={(e) => setType(e.target.value)}>
<MenuItem value="premierleague">Premier League</MenuItem>
<MenuItem value="formula1">Formula 1</MenuItem>
</Select>
{/*<ButtonGroup value={type} onClick={(e) => setType(e.target.value)}>
<Button value="premierleague">Premier League</Button>
<Button value="formula1">Formula 1</Button>
</ButtonGroup>*/}
</FormControl>
<Grid container spacing={3} className={classes.list}>
{events?.map((event, i) => (
<Grid item key={i} xs={12}>
<EventDetails event={event} />
</Grid>
))}
</Grid>
</div>
)
}
export default List;
EventDetails.jsx
import React from "react";
const EventDetails = ({ event }) => {
console.log(event)
return (
<h3>{event.league}</h3>
)
}
export default EventDetails;
You're not sending the events to List component.
Try changing in App.js:
return (
<>
<CssBaseline />
<Header />
<List events={events} />
</>
)

React - Firebase authentication and useContext

I'm trying to figure out the best way to create a global state variable that will hold a firebase authentication user id.
For example the below code would check if a user is logged in and then send them to welcome page if successful.
But I also need to setup up private routes on a different file, I want to be able to share the getId state. I read that useContext can do this but unsure how to implement it. Please advise, thanks
const [getId, setId] = useState("");
const login = async ( id ) => {
return setId(id);
};
firebase.auth().onAuthStateChanged((user) => {
if (user) {
login(user.uid).then(() => {
history.push("/welcome");
});
} else {
history.push("/");
}
});
const PrivateRoute = ({ getId, component: Component, ...rest }) => (
<Route
{...rest}
component={(props) =>
getId ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
}
/>
);
I'll give you my example to have an Auth Context. Here are the parts:
The _app.js file:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
The item of significance is the <AuthProvider> component. That's where the context is wrapped.
The AuthContent.js file:
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } from '../firebase'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
This is where the state is stored and accessed, including all of the helpers (signup, signout, login, etc).
How to use it:
import { Button, Card, CardHeader, CardContent, Link, TextField, Typography } from '#material-ui/core'
import { motion } from 'framer-motion'
import { useRef, useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { useRouter } from 'next/router'
export default function SignupForm() {
const router = useRouter()
const { signUp } = useAuth()
const [state, setState] = useState({
email: "",
password: "",
passwordConfirm: ""
})
const [error, setError] = useState("")
function handleForm(e) {
setState({
...state,
[e.target.name]: e.target.value
})
}
async function handleSubmit(e) {
if (state.password !== state.passwordConfim) {
setError("Passwords do not match")
}
await signUp(state.email, state.password)
.catch(err => console.log(JSON.stringify(err)) )
router.push("/account")
}
return(
<motion.div>
<Card >
<CardHeader title="Header" />
<CardContent>
<TextField label="email" name="email" variant="outlined" onChange={ handleForm } />
<TextField label="password" name="password" type="password" variant="outlined" onChange={ handleForm } />
<TextField label="Password Confirmation" name="passwordConfirm" type="password" variant="outlined" onChange={ handleForm } />
{error && <Alert severity="error" variant="filled" >{error}</Alert>}
<Button onClick={ handleSubmit }>
<Typography variant="button">Sign Up</Typography>
</Button>
</CardContent>
</Card>
</motion.div>
)
}
You import { useAuth } from your context (I usually put mine in a context folder) and then you can invoke instances of the variables inside the component by destructuring (e.g. const { currentUser, login } = useAuth())

Invalid Hook Call when executing anonymous function using button onClick

I am trying to create a submit button in React.
This is the code I have to handle the onClick event:
const handleEditProfileSubmit = () => {
const authContext = useContext(AuthContext);
if (newUserEntityDetails.username !== "") {
newUserEntityDetails.uid = authContext.user.uid;
axios.put(ENDPOINT + authContext.user.uid, newUserEntityDetails).then((res: any) => console.log(res)).catch((e: any) => console.log(e));
}
};
And this is the code I have for the button:
<Button onClick={handleEditProfileSubmit}>Submit</Button>
Which uses the Button material-ui component.
I have tried using onClick={() => {handleEditProfileSubmit}}, however this results in the submit button doing nothing.
I have also tried turning the handleEditProfileSubmit constant into a function and doing onClick={handleEditProfileSubmit()}, however this gets the same error.
I am unsure of what I am doing wrong. The axios API calls work fine in other parts of my code so I think it's not about the API call.
EDIT: This is the code for the entire component.
import React, {useContext} from "react";
import Navbar from "../components/navbar";
import { Redirect } from "react-router-dom";
import { AuthContext } from "../Auth";
import { Container } from "#material-ui/core";
import { Grid } from "#material-ui/core";
import { Paper, Card, CardContent } from "#material-ui/core";
import { MenuList, MenuItem} from "#material-ui/core";
import { Typography } from "#material-ui/core";
import { TextField } from "#material-ui/core";
import { Button } from "#material-ui/core";
const axios = require('axios');
const ENDPOINT = 'http://localhost:3000/api/user/';
var newUserEntityDetails = {
uid: "",
username: ""
}
const authContext = useContext(AuthContext);
const handleEditProfileSubmit = () => {
if (newUserEntityDetails.username !== "") {
newUserEntityDetails.uid = authContext.user.uid;
var a = axios.put(ENDPOINT + authContext.user.uid, newUserEntityDetails).then((res: any) => console.log(res)).catch((e: any) => console.log(e));
console.log(a);
}
};
const Settings = () => {
// If there is no user in the session
if (authContext.user == null) {
return(<Redirect to={"/login"} />);
} else {
return(
<>
<Navbar />
<Container maxWidth="lg">
<Grid container spacing={3} direction="row" style={{ minHeight: "90vh" }}>
<Grid item xs={3}>
<Paper>
<MenuList>
<MenuItem>
<Typography variant="body1">Profile</Typography>
</MenuItem>
<MenuItem>
<Typography variant="body1">Account Settings</Typography>
</MenuItem>
<MenuItem>
<Typography variant="body1">Delete Account</Typography>
</MenuItem>
</MenuList>
</Paper>
</Grid>
<Grid item xs={6}>
<Card>
<CardContent>
<Typography variant="subtitle2" gutterBottom>
Change Username
</Typography>
<TextField
id="outlined-helperText"
label="Change Username"
helperText="Username must be unique"
variant="outlined"
onChange = { input => {
newUserEntityDetails.username = input.target.value;
}}
/>
<Button onClick={() => {handleEditProfileSubmit}}>Submit</Button>
</CardContent>
</Card>
</Grid>
</Grid>
</Container>
</>
);
}
};
export default Settings;
P.S I have been told by my project manager to avoid using classes for components hence I am trying to write functional components
You're calling Hooks inside function, that's what causing the problem.
As per React docs: Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders
Declare hook top of your function, you should be fine!!.
...all imports
const Settings = () => {
const authContext = useContext(AuthContext); //This is what you need to do.
const handleEditProfileSubmit = () => {
if (newUserEntityDetails.username !== "") {
newUserEntityDetails.uid = authContext.user.uid;
var a = axios.put(ENDPOINT + authContext.user.uid, newUserEntityDetails).then((res: any) => console.log(res)).catch((e: any) => console.log(e));
console.log(a);
}
};
....
You need to have const authContext = useContext(AuthContext); outside the handleEditProfileSubmit.

React-redux Hooks and function component logic

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 };
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>
}

Resources