If the login is successful and a token is stored in localStorage, I should be redirected to a private route i.e /panel. If not, I should show the error message from ShowError().
Currently, my error message is being displayed if the login is not successful so it's all good. However, if there is a token present, there is still no redirection.
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
const [shouldRedirect, setShouldRedirect] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
return (
<ThemeProvider theme={colortheme}>
<Typography color='primary'>
Login Unsuccessful
</Typography>
</ThemeProvider>)
}
}
// function FormSubmitted(){
// setSubmitted(true);
// console.log('Form submitted');
// }
// function RedirectionToPanel(){
// console.log('check');
// if(submitted && localStorage.getItem('token')){
// console.log('Finall');
// return <Redirect to='/panel'/>
// }
// }
useEffect(() => {
if(localStorage.getItem('token')){
setShouldRedirect(true);
}
},[] );
function submitForm(LoginMutation: any) {
setSubmitted(true);
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
<br></br>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
)
}}
</Formik>
</div>
<Box mt={8}>
<Copyright />
</Box>
{/* {submitted && <Redirect to='/panel'/>} */}
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
My App.tsx:
const token = localStorage.getItem('token');
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/404'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
export default function App() {
return (
<div>
<BrowserRouter>
<Switch>
<Route exact path='/' component= {HomePage}></Route>
<Route path='/login' component= {LoginPage}></Route>
<Route path='/404' component= {Error404Page}></Route>
<PrivateRoute
path='/panel'
isAuthenticated={token}
component={PanelHomePage}
/>
<Redirect from='*' to='/404' />
</Switch>
</BrowserRouter>
</div>
);
}
Why do you use useState to redirect? Just redirect directly. And if you are using react-router it is good to use useHistory.
import { useHistory } from 'react-router-dom';
const history = useHistory();
useEffect(() => {
if(localStorage.getItem('token')){
history.push({
pathname: '/',
});
}
},[]);
If you still want to use useState you can create a new useEffect NS add that state into your dependency array.
const history = useHistory();
useEffect(() => {
if(localStorage.getItem('token')){
setShouldRedirect(true);
}
},[]);
useEffect(() => {
if(shouldRedirect){
history.push({
pathname: '/',
});
}
},[shouldRedirect]);
Related
I am trying to change a parent's component state from a child component's state.
This is the parent component:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
loggedin: false
}
this.updateLanguage = this.updateLanguage.bind(this)
this.logIn = this.logIn.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
logIn() {
this.setState({
loggedin: true
})
}
render() {
const { selectedLanguage, repos, error, loggedin } = this.state
console.log(loggedin)
return (
<Router>
<div className='container'>
<LanguagesNav
selected={selectedLanguage}
onUpdateLanguage={this.updateLanguage}
/>
<Route
exact path='/'
render={(props) => (
<Login
repos={repos}
selectedLanguage={selectedLanguage}
logIn={this.logIn}
/>
)}
/>
<Route
path='/dashboard'
render={(props) => (
<Dashboard
repos={repos}
selectedLanguage={selectedLanguage}
/>
)}
/>
<Route
path='/profile'
render={(props) => (
<Profile
repos={repos}
selectedLanguage={selectedLanguage}
/>
)}
/>
<Route path='/newdashboard'>
<DrawerPage />
</Route>
</div>
</Router>
)
}
}
As I saw on this answer to a similar question, I am passing a function setting the state from parent to child, and then, I call the function via props from the child component:
function LoginForm ({ repos, selected }) {
const languages = ['EU', 'ES', 'EN']
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://loginsso.ehu.es/login/images/forest.jpg)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src="https://loginsso.ehu.es/login/images/logo_UPV_peq.png"
/>
<br/>
<Box component="form" noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label={language.username}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label={language.password}
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label={language.remember}
/>
<Link
to={{
pathname: '/dashboard',
search: `?lang=${selected}`
}}
>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
// onClick={() => {
// alert('clicked');
// }}
>
{language.login}
</Button>
</Link>
<Grid container>
<Grid item xs>
<Link
to={{
pathname: '/newdashboard',
search: `?lang=${selected}`
}} variant="body2">
{language.forgot}
</Link>
</Grid>
<Grid item>
</Grid>
</Grid>
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
}
this.updateLanguage = this.updateLanguage.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
render() {
const { selectedLanguage, repos, error } = this.props
return (
<React.Fragment>
{this.props.logIn} //calling the parent's function
{error && <p>{error}</p>}
{repos && <LoginForm repos={repos} selected={selectedLanguage}/>}
</React.Fragment>
)
}
}
However, the state won't change.
Okay, I found a solution to my question. I tried this on the child component:
function LoginForm ({ repos, selected, onLogIn }) {
const languages = ['EU', 'ES', 'EN']
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://loginsso.ehu.es/login/images/forest.jpg)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src="https://loginsso.ehu.es/login/images/logo_UPV_peq.png"
/>
<br/>
<Box component="form" noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label={language.username}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label={language.password}
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label={language.remember}
/>
<Link
to={{
pathname: '/dashboard',
search: `?lang=${selected}`
}}
>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
// I call the child component's method which at the same time calls the parent's component's method.
onClick={() => {onLogIn()}}
>
{language.login}
</Button>
</Link>
<Grid container>
<Grid item xs>
<Link
to={{
pathname: '/newdashboard',
search: `?lang=${selected}`
}} variant="body2">
{language.forgot}
</Link>
</Grid>
<Grid item>
</Grid>
</Grid>
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null
}
this.updateLanguage = this.updateLanguage.bind(this)
this.logIn = this.logIn.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
// Created this function that calls the parent's logIn function.
logIn() {
this.props.logIn();
}
render() {
const { selectedLanguage, repos, error, logIn } = this.props
return (
<React.Fragment>
{error && <p>{error}</p>}
{/*I passed child component's method to the function component with the onLogIn attribute.*/}
{repos && <LoginForm repos={repos} selected={selectedLanguage} onLogIn={this.logIn}/>}
</React.Fragment>
)
}
}
Basically, I created a method on the child component that calls the method of the parent component.
I am not sure if I am using the right words to describe the solution, but I have used this solution in other parts of the project and it always works.
Feel free to comment with suggestions to explain my solution more correctly! :)
I am trying to create my own ErrorBoundary to catch all Applications and log it so someplace. Currently, I am using a MaterialUI Snackbar to show Errors.
However, the ErrorBoundary Component does log the Error, but the Application crashes anyway.
Here is a gif, of what I am trying to describe.
My Code for the Login Page:
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
error: { isPresent: false, message: {} },
password: '',
email: '',
validInput: false,
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
static contextType = UserDispatchContext
componentDidUpdate(prevProp, prevState) {
this.validateForm();
}
validateForm() {
let valid = this.state.email.length > 0 && this.state.password.length > 0;
if (valid !== this.state.validInput) {
this.setState({ validInput: valid });
}
}
async handleSubmit(e) {
//e.preventDefault();
const { dispatch } = this.context
const response = await callApi({
path: "/auth/local", method: "POST", body: {
identifier: this.state.email,
password: this.state.password
}
})
if (response.user) {
dispatch({ type: "LOGIN", user: response.user })
} else {
const error = { isPresent: true, message: response }
this.setState({ error })
}
}
handleChange = (prop) => (event) => {
this.setState({[prop]: event.target.value });
};
render() {
const { classes } = this.props;
if (this.state.error.isPresent) {
throw this.state.error.message
}
return (
<div style={{ width: "100%", height: "100%", display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<ReactTitle>Login</ReactTitle>
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<SvgIcon>
{coffeeBeans}
</SvgIcon>
</Avatar>
<Typography component="h1" variant="h5">
Login
</Typography>
<form className={classes.form}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
value={this.state.email}
onChange={this.handleChange('email')}
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={this.state.password}
onChange={this.handleChange('password')}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
</Grid>
</Grid>
<Button
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={!this.state.validInput}
onClick={() => this.handleSubmit()}
>
Login
</Button>
<Grid container justify="flex-end">
<Grid item>
<Link
to="/signup"
variant="body2">
New user? Sign up
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
</div>
)
}
}
And the ErrorBoundary:
class ErrorHandler extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, open: false, message: {} };
}
componentDidCatch(error, info) {
this.setState({ hasError: true, open: true, message: error });
console.log("error", error);
}
handleClose() {
this.setState({ hasError: false, open: false })
}
parseObjectProperties(obj, parse) {
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
this.parseObjectProperties(obj[k], parse)
} else if (obj.hasOwnProperty(k)) {
parse(k, obj[k])
}
}
}
getErrorMessage(errorObject) {
var message = "";
this.parseObjectProperties(errorObject, function (k, prop) {
if (k === "message")
message = prop
})
return message;
}
render() {
if (this.state.hasError) {
return (
<>
{this.props.children}
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
open={this.state.open}
onClose={this.handleClose}
autoHideDuration={4000}
>
<Alert onClose={this.handleClose} severity="error">
{this.getErrorMessage(this.state.message)}
</Alert>
</Snackbar>
</>
)
}
return this.props.children;
}
}
Edit: I forgot the part where I am surrounding my Pages with the ErrorBoundary.
<Router>
<Header />
<Content >
<ErrorHandler>
<Switch>
<PublicRoute exact path="/" component={Home} />
<PublicRoute restricted path="/login" component={Login} />
<PrivateRoute path="/profile" component={Profile} />
<PublicRoute restricted path="/signup" component={SignUp} />
</Switch>
</ErrorHandler>
</Content>
</Router>
I don't know what I am doing wrong. Has anyone an idea what I am doing wrong?
You're missing the getDerivedStateFromError in your error boundary which will update the ErrorHandler state and render the Snackbar. Check out the react docs for more info on error boundaries.
class ErrorHandler extends React.Component {
// ...
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error }
}
render() {
if (this.state.hasError) {
return (
<>
{this.props.children}
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
open={this.state.open}
onClose={this.handleClose}
autoHideDuration={4000}
>
<Alert onClose={this.handleClose} severity="error">
{this.getErrorMessage(this.state.message)}
</Alert>
</Snackbar>
</>
)
}
return this.props.children;
}
}
I have implemented Private Routing like this. If a token is present in the localStorage, private routes can be accessed. If not, we should redirect to the /404 page:
const token = localStorage.getItem('token');
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/404'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
But I am also using redirection in my LoginPage.
I want that if login is unsuccessful, the error message from ShowError() is shown. Instead of redirecting. This was working properly before I added the redirection. If the login is successful, I should go to /panel. However, right now, if the login is unsuccessful, I just go to /404. Instead of showing the error message. How can I fix this?
Login Screen:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
return (
<ThemeProvider theme={colortheme}>
<Typography color='primary'>
Login Unsuccessful
</Typography>
</ThemeProvider>)
}
}
function FormSubmitted(){
setSubmitted(true);
}
function RedirectionToPanel(){
if(submitted && localStorage.getItem('token')){
return <Redirect to='/panel'/>
}
}
function submitForm(LoginMutation: any) {
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);FormSubmitted();RedirectionToPanel()}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
<br></br>
<Grid container>
<Grid item xs>
</form>
)
}}
</Formik>
</div>
{submitted && <Redirect to='/panel'/>}
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
Edit::
I tried using this too:
if(localStorage.getItem('token')){
return <Redirect to='/panel'/>
}
});
but it gives me an error on the arrow that:
Argument of type '() => JSX.Element | undefined' is not assignable to parameter of type 'EffectCallback'.
Type 'Element | undefined' is not assignable to type 'void | (() => void | undefined)'.
Type 'Element' is not assignable to type 'void | (() => void | undefined)'.
Type 'Element' is not assignable to type '() => void | undefined'.
Type 'Element' provides no match for the signature '(): void | undefined'.ts(2345)
remove this line at the end of the container.
{submitted && <Redirect to='/panel'/>}
as it doesn't make sense to be there.
also, adjust the submit method to execute functions correctly.
function submitForm(LoginMutation: any) {
const { email, password } = state;
return LoginMutation({
variables: {
email,
password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
}).catch(console.error);
}
with promise being returned from the submitForm method, you can do the following.
onSubmit={e => {
e.preventDefault();
submitForm(LoginMutation).then((res: any) => {
RedirectionToPanel();
FormSubmitted();
});
}}
If login is successful, a token is returned and is stored in the local storage. In this case, I should redirect to a private route /panel. If login is unsuccessful, I should be able to show the error message from ShowError(). The error message functionality was working properly before I added the redirection.
This is my LoginPage.tsx
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
return (
<ThemeProvider theme={colortheme}>
<Typography color='primary'>
Login Unsuccessful
</Typography>
</ThemeProvider>)
}
}
function FormSubmitted(){
setSubmitted(true);
console.log('Form submitted');
}
function RedirectionToPanel(){
if(submitted && localStorage.getItem('token')){
return <Redirect to='/panel'/>
}
}
// useEffect(() => {
// if(localStorage.getItem('token')){
// return <Redirect to='/panel'/>
// }
// },[] );
function submitForm(LoginMutation: any) {
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);FormSubmitted();RedirectionToPanel()}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
<br></br>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
)
}}
</Formik>
</div>
{submitted && <Redirect to='/panel'/>}
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
Currently, if the login is unsuccessful, it automatically redirects to page /404. However, I want it to direct to /404 only if an invalid/private link is entered. In case of unsuccessful login, I want to show the error message.
I also tried to use UseEffect(), which is commented out in the code above but it keeps giving me this error on the arrow:
Argument of type '() => JSX.Element | undefined' is not assignable to parameter of type 'EffectCallback'.
Type 'Element | undefined' is not assignable to type 'void | (() => void | undefined)'.
Type 'Element' is not assignable to type 'void | (() => void | undefined)'.
Type 'Element' is not assignable to type '() => void | undefined'.
Type 'Element' provides no match for the signature '(): void | undefined'.ts(2345)
here is how my private routing is implemented in the App.tsx
const token = localStorage.getItem('token');
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/404'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
If I comment out the {submitted && <Redirect to='/panel'/>}, I get the error message on unsuccessful login but then there's no redirection even when the login is successful.
Edited Code:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
const [shouldRedirect, setShouldRedirect] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
return (
<ThemeProvider theme={colortheme}>
<Typography color='primary'>
Login Unsuccessful
</Typography>
</ThemeProvider>)
}
}
// function FormSubmitted(){
// setSubmitted(true);
// console.log('Form submitted');
// }
function RedirectionToPanel(){
console.log('check');
if(submitted && localStorage.getItem('token')){
console.log('Finall');
return <Redirect to='/panel'/>
}
}
useEffect(() => {
if(localStorage.getItem('token')){
setShouldRedirect(true);
}
},[] );
function submitForm(LoginMutation: any) {
setSubmitted(true);
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);RedirectionToPanel()}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
</form>
)
}}
</Formik>
</div>
{/* {submitted && <Redirect to='/panel'/>} */}
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
I believe that returning Redirect component shouldn't be called in useEffect hook or inside any function.
You should have something like that:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [shouldRedirect, setShouldRedirect] = useState(false);
useEffect(() => {
if(localStorage.getItem("token")) {
setShouldRedirect(true);
}
}, []);
function submitForm(LoginMutation: any) {
setSubmitted(true);
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
setShouldRedirect(true);
})
.catch(console.log)
}
}
if(shouldRedirect) return <Redirect to="/panel" />;
return (
... rest of code
);
}
I'm kinda new to react and was wondering how to internally redirect your pages in reactjs. I have two pages called register and register2. In register page, I just check if the email exists in the database or not and if it doesn't exist then it redirects to register2 page for creating the full account with username and password. However, in the address bar, it kinda looks ugly to show something like register2. So I was wondering if there is any way through which I can internally redirect without changing the address in the address bar to register2 such that it stays as register throughout the whole account creation process.
I created a codesandbox to show the issue
register.js
import React, { useState } from "react";
import { Formik } from "formik";
import TextField from "#material-ui/core/TextField";
import * as Yup from "yup";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import CssBaseline from "#material-ui/core/CssBaseline";
import Link from "#material-ui/core/Link";
import Grid from "#material-ui/core/Grid";
import Box from "#material-ui/core/Box";
import LockOutlinedIcon from "#material-ui/icons/LockOutlined";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core/styles";
import Container from "#material-ui/core/Container";
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
<Link color="inherit" href="sad">
New1
</Link>{" "}
{new Date().getFullYear()}
{"."}
</Typography>
);
}
const useStyles = makeStyles(theme => ({
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center"
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
form: {
width: "100%",
marginTop: theme.spacing(1)
},
submit: {
margin: theme.spacing(3, 0, 2)
}
}));
const Reg = props => {
const classes = useStyles();
const [loginError, setLoginError] = useState("");
const [changed, setChanged] = useState(false);
const [newpage, setNew] = useState(false);
const handleSubmit = async (values, { setSubmitting }) => {
const { email } = values;
var body = {
email: email
};
console.log(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/emailcheck";
try {
const response = await fetch(url, options);
const text = await response.text();
setSubmitting(false);
setChanged(false);
setNew(true);
console.log(text);
if (newpage) {
props.history.push({
pathname: "/register2",
state: { email }
});
// props.history.push(`/register2/${email}`);
} else if (text === "exists") {
props.history.push(`/`);
} else {
setLoginError("Email is invalid");
}
} catch (error) {
console.error(error);
}
};
return (
<Formik
initialValues={{ email: "" }}
onSubmit={handleSubmit}
//********Using Yup for validation********/
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required")
})}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<>
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form
className={classes.form}
onSubmit={handleSubmit}
noValidate
>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
value={values.email}
label="Email Address"
name="email"
autoComplete="email"
onChange={e => {
setChanged(true);
handleChange(e);
}}
onBlur={handleBlur}
className={errors.email && touched.email && "error"}
/>
{errors.email && touched.email && (
<div className="input-feedback" style={{ color: "red" }}>
{errors.email}
</div>
)}
{!changed && loginError && (
<div style={{ color: "red" }}>
<span>{loginError}</span>
</div>
)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={isSubmitting}
>
Next
</Button>
<Grid container justify="flex-end">
<Grid item>
<Link href="/" variant="body2">
Already have an account? Sign in
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
</>
);
}}
</Formik>
);
};
export default Reg;
register2.js
import React, { useState } from "react";
import { Formik } from "formik";
import TextField from "#material-ui/core/TextField";
import { withRouter, useHistory } from "react-router-dom";
import * as Yup from "yup";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import CssBaseline from "#material-ui/core/CssBaseline";
import Link from "#material-ui/core/Link";
import Box from "#material-ui/core/Box";
import LockOutlinedIcon from "#material-ui/icons/LockOutlined";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core/styles";
import Container from "#material-ui/core/Container";
function Copyright() {
return (
<Typography va riant="body2" color="textSecondary" align="center">
<Link color="inherit" href="sad">
New
</Link>{" "}
{new Date().getFullYear()}
{"."}
</Typography>
);
}
const useStyles = makeStyles(theme => ({
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center"
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
form: {
width: "100%",
marginTop: theme.spacing(1)
},
submit: {
margin: theme.spacing(3, 0, 2)
}
}));
const Reg2 = props => {
const classes = useStyles();
const [loginError, setLoginError] = useState("");
const history = useHistory();
const [changed, setChanged] = useState(false);
const handleSubmit = async (values, { setSubmitting }) => {
const { username, password } = values;
var body = {
username: username,
password: password,
email: history.location.state.email
};
console.log(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/register";
try {
const response = await fetch(url, options);
const text = await response.text();
setSubmitting(false);
setChanged(false);
console.log(text);
if (text === "verifyemail") {
props.history.push({
pathname: "/verifyOtp",
state: { email: body.email }
});
// props.history.push(`/verifyOtp/${username}`);
} else {
setLoginError("Username or Password is incorrect");
}
} catch (error) {
console.error(error);
}
};
return (
<Formik
initialValues={{ username: "", password: "", confirmPassword: "" }}
onSubmit={handleSubmit}
//********Using Yup for validation********/
validationSchema={Yup.object().shape({
username: Yup.string().required("Required"),
password: Yup.string()
.required("No password provided.")
.min(8, "Password is too short - should be 8 chars minimum.")
.matches(/(?=.*[0-9])/, "Password must contain a number.")
.matches(
/(?=.*[●!"#$%&'()*+,\-./:;<=>?#[\\\]^_`{|}~])/,
"Password must contain a symbol."
),
confirmPassword: Yup.string()
.required("Enter to confirm password")
.oneOf([Yup.ref("password"), null], "Password do not match")
})}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<>
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Enter Info
</Typography>
<form
className={classes.form}
onSubmit={handleSubmit}
noValidate
>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="username"
value={values.username}
label="username"
name="username"
autoComplete="username"
onChange={e => {
setChanged(true);
handleChange(e);
}}
onBlur={handleBlur}
className={errors.username && touched.username && "error"}
/>
{errors.username && touched.username && (
<div className="input-feedback" style={{ color: "red" }}>
{errors.username}
</div>
)}
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
value={values.password}
label="Password"
type="password"
id="password"
onBlur={handleBlur}
autoComplete="current-password"
className={errors.password && touched.password && "error"}
onChange={e => {
setChanged(true);
handleChange(e);
}}
/>
{errors.password && touched.password && (
<div className="input-feedback" style={{ color: "red" }}>
{errors.password}
</div>
)}
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="confirmPassword"
value={values.confirmPassword}
type="password"
label="Confirm Password"
id="confirmPassword"
onBlur={handleBlur}
autoComplete="confirmPassword"
className={
errors.confirmPassword &&
touched.confirmPassword &&
"error"
}
onChange={e => {
setChanged(true);
handleChange(e);
}}
/>
{errors.confirmPassword && touched.confirmPassword && (
<div className="input-feedback" style={{ color: "red" }}>
{errors.confirmPassword}
</div>
)}
{!changed && loginError && (
<div style={{ color: "red" }}>
<span>{loginError}</span>
</div>
)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={isSubmitting}
>
Next
</Button>
</form>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
</>
);
}}
</Formik>
);
};
export default withRouter(Reg2);
you could make a parent component for register and register2 and store your logic there in which component to display
function MyComponent() {
const [email, setEmail] = useState(null)
useEffect(() => {
(async () => {
const response = await yourApiCall()
setEmail(response)
})()
}, [])
return email ? <Register /> : <Register2 />
}
Initially show the register component or code of that sort.
Call an api, if the email exists, store flag value in that and do conditional rendering for another component or code of register2.
Initially, this.state.isEmailExist : false,
After api call change flag value accordingly. If true then render Register2.
const contents = if (!this.state.isEmailExist) {
return (
<Register />
);
} else {
return (
<Register2 />
)
}
return(
<div>{contents}</div>
)
Maybe you are looking for this
window.open("https://www.youraddress.com","_self");
Make sure to setup the validation.