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>
}
Related
I am trying to display new comments instantly when they are created. Currently, it doesn't work.
This is me code. On my PostPage, I added "fetchComments={fetchComments}" to my CommentForm tag. And on my CommentForm, I used props.fetchComments() in the handleSubmit.
Do you have a solution for this ?
My PostPage (parent component) :
import React, { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { API_URL } from '../config'
import { Skeleton } from '#material-ui/lab'
import { Grid, Button } from '#material-ui/core'
import { AiFillCaretLeft } from "react-icons/ai"
import List from '#material-ui/core/List'
import ListItem from '#material-ui/core/ListItem'
import ListItemText from '#material-ui/core/ListItemText'
import ListItemAvatar from '#material-ui/core/ListItemAvatar'
import Avatar from '#material-ui/core/Avatar'
import Typography from '#material-ui/core/Typography'
import CommentForm from '../Components/Forms/CommentForm'
import PostsAPI from '../Services/PostsAPI'
import CommentsAPI from '../Services/CommentsAPI'
export default function PostPage() {
const {id} = useParams()
const [post, setPost] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [comments, setComments] = useState([])
const fetchPost = async () => {
const data = await PostsAPI.findOne(id);
setPost(data)
setIsLoading(true)
}
const fetchComments = async (props) => {
try{
const comments = await CommentsAPI.findAll()
setComments(comments.data)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
fetchPost();
fetchComments();
}, [])
return (
<div>
<nav>
<Link to="/">
<Button variant="contained" color="primary"><AiFillCaretLeft /><span>Back</span></Button>
</Link>
</nav>
<Grid container spacing = {2}>
<Grid item sm={6}>
<div className='postImg'>
{isLoading ? <img src={API_URL + post.data.attributes.image.data[0].attributes.formats.small.url} alt={post.data.attributes.title} /> : <Skeleton variant="rect" width="100%" height={400} />}
</div>
</Grid>
<Grid item sm={6}>
<h1>{isLoading ? post.data.attributes.title : <Skeleton variant="text" width={300} height={80} />}</h1>
<p>{isLoading ? post.data.attributes.content :
<>
<Skeleton variant="text" height={25}/>
<Skeleton variant="text" width="60%" height={25}/>
</>
}</p>
</Grid>
</Grid>
<Grid container spacing={2}>
<Grid item md={6}>
<CommentForm fetchComments={fetchComments} />
</Grid>
<Grid item md={6}>
{<List>
{comments.map((comment, i) => (
<ListItem key={i} alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Avatar" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary={comment.attributes.pseudo}
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
color="textPrimary"
>
</Typography>
"{comment.attributes.content}"
</React.Fragment>
}
/>
</ListItem>
))}
</List>}
</Grid>
</Grid>
</div>
)
}
The CommentForm (child component) :
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import TextField from '#material-ui/core/TextField';
import { Button } from '#material-ui/core'
import CommentsAPI from '../../Services/CommentsAPI'
export default function CommentForm(props) {
const [comment, setComment] = useState({})
const {id} = useParams()
const handleSubmit = async (event) => {
event.preventDefault();
try {
CommentsAPI.create(JSON.parse(`{"data":${JSON.stringify(comment)}}`));
props.fetchComments()
} catch (error) {
console.log(error)
}
}
const handleChange = (event) => {
const {name, value} = event.currentTarget
setComment({
...comment,
[name]: value,
"post":id
})
}
return (
<form onSubmit={handleSubmit}>
<div>
<TextField
id="pseudo"
label="Pseudo"
type="text"
onChange={handleChange}
name="pseudo"
/>
</div>
<div>
<TextField
id="comment"
label="Comment"
multiline
minRows={2}
onChange={handleChange}
name="content"
/>
</div>
<div>
<Button variant="contained" color="primary" type="submit">
Send
</Button>
</div>
</form>
)
}
Thank you for your help !
You can do something like this.
const [apiData, setAPIData] = useState();
const handleSubmit = async (event) => {
event.preventDefault();
try {
CommentsAPI.create(JSON.parse(`{"data":${JSON.stringify(comment)}}`));
const data = await props?.fetchComments();
//--
setAPIData(data);
} catch (error) {
console.log(error)
}
}
return (
{ data.map( item => {----} )
}
)
Thanks.
~ Storm In Talent
I want to send State value to another component. They are at same directory. Basically I want to get "selectedvalue" from "Conversation.js" to "Openconversation.js".
Everything's fine but I just need to send that State value (selectedvalue) to Openconversation.js
./components/Conversation.js:
import React, {useState} from 'react'
import { ListGroup } from 'react-bootstrap'
import { useConversations } from '../contexts/ConversationsProvider';
export default function Conversations() {
const { conversations, selectConversationIndex } = useConversations()
**const [selectedvalue, setSelectedValue] = useState('')**
return (
<ListGroup variant="flush">
{conversations.map((conversation, index) => (
<ListGroup.Item
key={index}
action
onClick={() => {selectConversationIndex(index) && setSelectedValue(conversation.recipients.map(r => r.name).join(', '))} }
active={conversation.selected}
>
{conversation.recipients.map(r => r.name).join(', ')}
</ListGroup.Item>
))}
</ListGroup>
)
}
./components/Openconversation.js:
import React, { useState, useCallback, useEffect } from 'react'
import { Form, InputGroup, Button } from 'react-bootstrap'
import { useConversations } from '../contexts/ConversationsProvider';
import Axios from 'axios';
export default function OpenConversation() {
const [text, setText] = useState('');
const [curuser, setCurUser] = useState('');
const [oldchats, setOldChats] = useState([]);
const setRef = useCallback(node => {
if (node) {
node.scrollIntoView({ smooth: true })
}
}, [])
useEffect(() => {
Axios.get('http://localhost:3001/api/get').then((response) =>{
setOldChats(response.data);
})
}, [])
useEffect(()=>{
fetch('http://localhost:8000/api/current_user/', {
headers: {
Authorization: `JWT ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(json => {
setCurUser(json.id);
});
},[])
const { sendMessage, selectedConversation } = useConversations()
function handleSubmit(e) {
e.preventDefault()
sendMessage(
selectedConversation.recipients.map(r => r.id),
text,
selectedConversation.recipients.map(r => {
Axios.post("http://localhost:3001/api/insert", {
content: text,
sender: curuser,
recipient: r.id,
}).then(() => {
alert("saved on MySQL Database!");
});
})
)
setText('')
}
return (
<div className="d-flex flex-column flex-grow-1">
{selectedConversation.messages.map((message, index) => {
const lastMessage = selectedConversation.messages.length - 1 === index
return (
<div>
{oldchats.map(val => {
##### I NEED THAT SELECTEDVALUE DATA FROM CONVERSATION.JS ########
return (val.sender== curuser && val.recipient == selectedvalue) ?
<h2>{val.content}</h2>
:
<div></div>
})}
</div>
)
})}
</div>
</div>
<Form onSubmit={handleSubmit}>
<Form.Group className="m-2">
<InputGroup>
<Form.Control
as="textarea"
required
value={text}
onChange={e => setText(e.target.value)}
/>
<InputGroup.Append>
<Button type="submit">Send</Button>
</InputGroup.Append>
</InputGroup>
</Form.Group>
</Form>
</div>
)
}
It looks like in your Openconversation.js file you're not importing useContext from react and then assigning it to something like:
const conversationProviderContext = useContext(ConversationsProvider);
Also make sure that you're using the <ConversationsProvider.Provider/> component higher up above the <Openconversation/> component.
You should probably refer to the docs: https://reactjs.org/docs/context.html
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
The following code caused useEffect() to be called infinitely. Why is that the case? Removing drawingboarditems from the useEffect dependencies array solves the infinite loop, but as a result DrawingBoard will not automatically rerender whenenver a user adds an item to the database.
DrawingBoard.jsx
export default function DrawingBoard() {
const [drawingboarditems, setdrawingboarditems] = useState([]);
const currentUser = useContext(CurrentUserContext);
const [loading, setLoading] = useState(true);
const classes = useStyles();
useEffect(() => {
if (currentUser) {
const items = [];
//retrieving data from database
db.collection("drawingboarditems")
.where("userID", "==", currentUser.id)
.get()
.then((query) => {
query.forEach((doc) => {
items.push({
id: doc.id,
...doc.data(),
});
});
setdrawingboarditems(items);
setLoading(false);
});
}
}, [currentUser, drawingboarditems]);
return (
<>
{loading == false ? (
<Container>
<Masonry
breakpointCols={breakpoints}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{drawingboarditems.map((item) => (
<div>
<Note item={item} />
</div>
))}
<Note form />
</Masonry>
</Container>
) : (
<div className={classes.root}>
<CircularProgress />
</div>
)}
</>
);
Note.jsx
import React from "react";
import { Card, CardHeader, IconButton, makeStyles } from "#material-ui/core";
import MoreVertIcon from "#material-ui/icons/MoreVert";
import Form from "./Form";
import CardWindow from "./CardWindow";
const useStyles = makeStyles((theme) => ({
card: {
backgroundColor: theme.palette.secondary.main,
margin: theme.spacing(1, 0),
},
}));
export default function Note({ item, form }) {
const classes = useStyles();
return (
<Card className={classes.card}>
{form ? (
<Form />
) : (
<CardHeader
action={
<CardWindow/>
}
title={item.title}
/>
)}
</Card>
);
}
Form.jsx
import React, { useContext } from "react";
import TextField from "#material-ui/core/TextField";
import { makeStyles } from "#material-ui/core/styles";
import AddCircleOutlineIcon from "#material-ui/icons/AddCircleOutline";
import IconButton from "#material-ui/core/IconButton";
import { db } from "../FireStore";
import { CurrentUserContext } from "../utils/Context";
const useStyles = makeStyles((theme) => ({
form: {
"& .MuiTextField-root": {
margin: theme.spacing(1),
width: "70%", // 70% of card in drawing board
},
},
}));
export default function Form() {
const classes = useStyles();
const [value, setValue] = React.useState("");
const currentUser = useContext(CurrentUserContext);
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
if (value) {
event.preventDefault();
db.collection("drawingboarditems").add({
title: value,
userID: currentUser.id,
});
setValue("");
}
};
return (
<form className={classes.form} noValidate autoComplete="off">
<div>
<TextField
id="standard-textarea"
placeholder="Add item"
multiline
onChange={handleChange}
value={value}
/>
<IconButton aria-label="add" onClick={handleSubmit}>
<AddCircleOutlineIcon fontSize="large" />
</IconButton>
</div>
</form>
);
}
In your case I would move the logic to the DrawingBoard component, and would pass props to the children, so when a children adds an item, the main component would know to refresh the list of items.
Example (not tested):
Extract the logic to work with FireBase to functions. In that way they would be more re-usable, and would not add clutter to your code.
const drawingboarditemsCollection = 'drawingboarditems';
function getAllNotes(userID) {
return db.collection(drawingboarditemsCollection)
.where("userID", "==", userID)
.get()
.then((query) => {
return query.map(doc => {
items.push({
id: doc.id,
...doc.data(),
});
});
});
}
function addNote(userID, title) {
return db.collection(drawingboarditemsCollection).add({
title,
userID,
});
}
The DrawingBoard component should handle the connection with the server, and should pass functions as props to children:
export default function DrawingBoard() {
const [drawingboarditems, setdrawingboarditems] = useState([]);
const currentUser = useContext(CurrentUserContext);
const [loading, setLoading] = useState(true);
const classes = useStyles();
// add the logic to get notes by
const getNotes = useCallback(() => {
setLoading(true);
getAllNotes(currentUser.id)
.then(items => {
setdrawingboarditems(items);
})
.finally(=> {
setLoading(false);
});
}, [currentUser]);
// create the submit handler
const handleSubmit = value => {
addNote(currentUser.id, value)
.then(getNotes); // after adding a note update the items
}
// initial get notes, or when currentUser changes
useEffect(() => {
getNotes();
}, [getNotes]);
return (
<>
{loading == false ? (
<Container>
<Masonry
breakpointCols={breakpoints}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{drawingboarditems.map((item) => (
<div>
<Note item={item} />
</div>
))}
<Note form onSubmit={handleSubmit} />
</Masonry>
</Container>
) : (
<div className={classes.root}>
<CircularProgress />
</div>
)}
</>
);
}
Pass the onSubmit function to Form:
export default function Note({ item, form, onSubmit }) {
const classes = useStyles();
return (
<Card className={classes.card}>
{form ? (
<Form onSubmit={onSubmit} />
) : (
<CardHeader
action={
<CardWindow/>
}
title={item.title}
/>
)}
</Card>
);
}
Use onSubmit to handle the submission:
export default function Form({ onSubmit }) {
const classes = useStyles();
const [value, setValue] = React.useState("");
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
if (value) {
event.preventDefault();
onSubmit(value); // let the parent handle the actual update
setValue("");
}
};
return ( ... );
}
I am trying to change the button based on if a user is logged in or not. the functionality is managed from MyNav.js. if the user is logged in I show the sign-out button, otherwise, I show the sign-in and sign-up button. It would be great if you can guide what part I am doing wrong.
Warning:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at LoginSigninModal (http://localhost:3000/static/js/main.chunk.js:530:81)
at MenuButtons
MyNav.js
import React, { useEffect, useState } from 'react'
import Nav from 'react-bootstrap/Nav'
import Navbar from 'react-bootstrap/Navbar'
// import NavDropdown from 'react-bootstrap/NavDropdown'
import { Link } from 'react-router-dom'
import Searchfield from '../product_search_components/Searchfield'
import LoginSignout from '../login_components/LoginSignout'
import MenuButtons from './MenuButtons'
export default function MyNav(props) {
var isSigned
if (localStorage.getItem('scs_user_logged_in')){
isSigned = true
}else{
isSigned = false
}
const [signedIn, setSignedIn] = useState(isSigned)
var menu
if (signedIn) {
menu = <LoginSignout setSignedIn={setSignedIn}/>
} else {
menu = <MenuButtons setSignedIn={setSignedIn}/>
}
useEffect(()=>{
},[signedIn])
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand><span style={{ fontWeight: 'bold' }}>Test</span></Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Link className='nav-link' to="/">Home</Link>
<Link className='nav-link' to="/about">About</Link>
<Link className='nav-link' to="/todo">Todo</Link>
{/* <NavDropdown title="Dropdown" id="basic-nav-dropdown">
<NavDropdown.Item>Action</NavDropdown.Item>
<NavDropdown.Item>Another action</NavDropdown.Item>
<NavDropdown.Item>Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item>Separated link</NavDropdown.Item>
</NavDropdown> */}
</Nav>
</Navbar.Collapse>
<Searchfield search_item={props.search_item} setSearchItem={props.setSearchItem} />
{menu}
</Navbar>
)
}
MenuButtons.js
import LoginSigninModal from '../login_components/LoginSigninModal'
import LoginSignupModal from '../login_components/LoginSignupModal'
export default function MenuButtons(props) {
return (
<>
<LoginSigninModal {...props}/>
<LoginSignupModal />
</>
)
}
LoginSigninModal.js
import React, { useState, useEffect } from 'react'
import Button from 'react-bootstrap/Button'
import Modal from 'react-bootstrap/Modal'
import Form from 'react-bootstrap/Form'
import Alert from 'react-bootstrap/Alert'
import Spinner from 'react-bootstrap/Spinner'
import { baseURL } from './../assets/axiosInstance'
import axios from 'axios'
// import LoginSignupModal from './LoginSignupModal'
export default function LoginSigninModal(props) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const [localSigned, setLocalSigned] = useState(false)
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const errorEmail = false
const [apiCalling, setApiCalling] = useState(false)
const [messageOnSubmit, setMessageOnSubmit] = useState({
type: "",
message: ""
})
useEffect(() => {
props.setSignedIn(localSigned)
}, [localSigned, props])
function handleSubmit() {
setApiCalling(true)
const getData = async () => {
await axios.post(baseURL + 'auth/login', {
email: email,
password: password,
})
.then((response) => {
localStorage.setItem('scs_token', response.data['access_token'])
localStorage.setItem('scs_user', JSON.stringify(response.data['user']))
localStorage.setItem('scs_user_logged_in', true)
setLocalSigned(true)
setMessageOnSubmit({
type: "success",
message: "Login Successful"
})
})
.catch((error) => {
console.log(error.response.status)
if(error.response.status === 401){
setMessageOnSubmit({
type: "danger",
message: "Login failed due to wrong password."
})
}else if(error.response.status === 422){
setMessageOnSubmit({
type: "danger",
message: "Password needs to be at least 6 characters long."
})
}
})
.then(()=>{
setApiCalling(false)
})
}
getData()
}
return (
<>
<Button variant="primary" onClick={handleShow} style={{ marginLeft: '5px' }}>
Signin
</Button>
<Modal
show={show}
onHide={handleClose}
backdrop="static"
keyboard={false}
>
<Modal.Header closeButton>
<Modal.Title>Signin</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control value={email} onChange={(data) => setEmail(data.target.value)} type="email" placeholder="xyz#email.com" />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control value={password} onChange={(data) => setPassword(data.target.value)} type="password" placeholder="Password" />
</Form.Group>
{(
messageOnSubmit['type'] !== ""
?
<Alert variant={messageOnSubmit['type']}>
{messageOnSubmit['message']}
</Alert>
:
"")}
<Button variant="primary" type="button" disabled={errorEmail} active={!errorEmail} onClick={handleSubmit}>Login {apiCalling && <Spinner animation="border" size="sm" />}</Button>
<Button variant="secondary" onClick={handleClose} style={{ marginLeft: "5px" }}>Close</Button>
</Form>
</Modal.Body>
<Modal.Footer>
</Modal.Footer>
</Modal>
</>
);
}
http://localhost:3000/static/js/main.chunk.js:530:81 - (from the warning message)
function LoginSigninModal(props) {
_s();
const [show, setShow] = Object(react__WEBPACK_IMPORTED_MODULE_1__["useState"])(--error marked here--)(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
The issue was in the following code block of LoginSigninModal.js
.then((response) => {
localStorage.setItem('scs_token', response.data['access_token'])
localStorage.setItem('scs_user', JSON.stringify(response.data['user']))
localStorage.setItem('scs_user_logged_in', true)
setLocalSigned(true)
setMessageOnSubmit({
type: "success",
message: "Login Successful"
})
})
When I successfully logged in, I change localSigned state. which unmounts the login modal component. I then try to update the message on that component. as the component is already unmounted, was getting the error. removing the setMessageonSubmit or calling set setLocalSigned after the setMessageSubmit, fixes the issue.
Correct code will be:
.then((response) => {
localStorage.setItem('scs_token', response.data['access_token'])
localStorage.setItem('scs_user', JSON.stringify(response.data['user']))
localStorage.setItem('scs_user_logged_in', true)
setMessageOnSubmit({
type: "success",
message: "Login Successful"
})
setLocalSigned(true)
})
or
.then((response) => {
localStorage.setItem('scs_token', response.data['access_token'])
localStorage.setItem('scs_user', JSON.stringify(response.data['user']))
localStorage.setItem('scs_user_logged_in', true)
setLocalSigned(true)
})
In your LoginSigninModal component, you should call your getData in a useEffect function, like this:
useEffect(() => {
getData();
},[]);