Using react-private-route when token is in local storage - javascript

I am trying to create private routes using react-private-route to lead a user to an authorized /panel page if the authentication is successful:
https://github.com/hansfpc/react-private-route
For authentication, the submit button sends a mutation request to the graphql backend. If the authentication is succesful, the backend then sends back a token, which is stored in localStorage.
Here's what my code for my login page looks like: Do I need to make amends here?
const LoginMutation = gql`
mutation LoginMutation($email: String!, $password: String!) {
loginEmail(email: $email, password: $password)
}
`;
const schema = Yup.object({
email: Yup
.string()
.email('Invalid Email')
.required('Please Enter your Email'),
password: Yup
.string()
.required('Please Enter your password')
});
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
loggedIn: false,
});
function submitForm(LoginMutation: any) {
const { email, password } = state;
console.log(email, password)
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<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")}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
>
Submit</Button>
</form>
)
}}
</Formik>
</div>
)
}
</Mutation>
);
}
export default LoginPage;
Now I don't quite understand how I should modify this code in a way that I can set my private route if the token is being returned.
Some conditional statements perhaps but how and where? The example on the GitHub repo of react-private-route has isAuthenticated={!!isLoggedIn() /* this method returns true or false */}property. How can I create this function according to my code?
Here are few example solutions for Typescript but I don't understand how to use them with local storage How to rewrite the protected/private route using TypeScript and React-Router 4 and 5?

You can use like this if you have token stored in localstorage.
import { Redirect } from "react-router";
const PrivateRoute = ({ component: Component, ...props }) => {
return (
<Route
{...props}
render={innerProps =>
localStorage.getItem("Token") ? (
<Component {...innerProps} />
) : (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
)
}
/>
);

Related

React - Unhandled Rejection (TypeError): e.preventDefault is not a function

I'm getting this error below when I try to implement an Axios post with react-hook-form:
Unhandled Rejection (TypeError): e.preventDefault is not a function
The problem started occuring when I added onSubmit={handleSubmit(handleSubmitAxios)} to my <form>. Basically, I want my form to be controlled by react-hook-form, using my custom handleSubmitAxios post call that communicates with my backend.
This is for my sign-in component, currently just testing out the functionality of react-hook-form however the e.preventDefault message is confusing me.
export default function SignIn() {
const { register, control, errors: fieldsErrors, handleSubmit } = useForm();
const history = useHistory();
const initialFormData = Object.freeze({
email: "",
password: "",
});
const [formData, updateFormData] = useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
});
};
const dispatch = useDispatch();
const handleSubmitAxios = (e) => {
e.preventDefault();
console.log(formData);
axiosInstance
.post(`auth/token/`, {
grant_type: "password",
username: formData.email,
password: formData.password,
})
.then((res) => {
console.log(res);
localStorage.setItem("access_token", res.data.access_token);
localStorage.setItem("refresh_token", res.data.refresh_token);
history.push("/");
window.location.reload();
dispatch(
login({
name: formData.email,
password: formData.password,
loggedIn: true,
})
);
});
};
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form
className={classes.form}
noValidate
onSubmit={handleSubmit(handleSubmitAxios)}
>
<FormControl fullWidth variant="outlined">
<Controller
name="email"
as={
<TextField
variant="outlined"
margin="normal"
inputRef={register}
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
onChange={handleChange}
helperText={
fieldsErrors.email ? fieldsErrors.email.message : null
}
error={fieldsErrors.email}
/>
}
control={control}
defaultValue=""
rules={{
required: "Required",
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
message: "invalid email address",
},
}}
/>
</FormControl>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Sign In
</Button>
</form>
</div>
</Container>
);
}
Thank you for any help or guidance on how I can solve the original error!
As per the docs, First parameter is the data or errors object, second parameter is the form event.
((data: Object, e?: Event) => void, (errors: Object, e?: Event) => void) => Function
In your case is e is the data, that's why you're getting e.preventDefault is not a function error.
Try like this
const handleSubmitAxios = (data, e) => {
e.preventDefault(); // Actually, you don’t need to call this, Because it’s already called inside react hook form.
console.log(data)
.....
But, The react-hook-form is already preventing the default form event, Not sure why you wanna do that again. Check this screen shot once and demo too

handleSubmit with Formik using TypeScript

I have successfully written a Login component in React using plain Javascript. I would now like to convert this component to TypeScript. I have defined some types and also threw in some "any" just to initially compile. Unfortunately, the onClick parameter in my submit button throws the following error:
TS2322: Type '(e?: FormEvent | undefined) => void' is not assignable to type '(event: MouseEvent<HTMLElement, MouseEvent>)
=> void'.
Here is the relevant code:
class Login extends React.Component<LoginProps, LoginState> {
constructor(props) {
super(props);
this.login = this.login.bind(this);
}
async login(values) {
const user = {
email: values.email,
password: values.password,
};
const query = `mutation userLogin(
$user: UserLoginInputs!
) {
userLogin(
user: $user
) {
token
}
}`;
const data: any = await graphQLFetch(query, { user });
if (data && data.userLogin.token) {
const decoded: any = jwt.decode(data.userLogin.token);
const { onUserChange } = this.props;
onUserChange({ loggedIn: true, givenName: decoded.givenName });
const { history } = this.props;
history.push('/');
}
}
render() {
return (
<Formik
onSubmit={this.login}
validationSchema={loginSchema}
initialValues={{
email: '',
password: '',
}}
>
{({
handleSubmit,
handleChange,
values,
}) => (
<Card>
<Card.Body>
<Form>
<Form.Group>
<Form.Label>E-mail</Form.Label>
<Form.Control
name="email"
value={values.email}
onChange={handleChange}
/>
</Form.Group>
<Form.Group>
<Form.Label>Password</Form.Label>
<Form.Control
name="password"
value={values.password}
onChange={handleChange}
/>
</Form.Group>
</Form>
<Button
type="button"
variant="primary"
onClick={handleSubmit}
>
Submit
</Button>
</Card.Body>
</Card>
)}
</Formik>
);
}
}
I'm new to TypeScript and don't fully understand why an error occurs regarding e versus event when the login function does not explicitly reference either of those. Can someone please help me assign types to my handleSubmit function (aka login)?
I hope that example could help you to resolve your issue
import { Field, Form, Formik } from 'formik';
import * as React from 'react';
import './style.css';
interface MyFormValues {
firstName: string;
}
export default function App() {
const initialValues: MyFormValues = { firstName: '' };
const getSubmitHandler =
() =>
(values, formik) => {
console.log({ values, formik });
alert(JSON.stringify(values, null, 2));
formik.setSubmitting(false);
};
return (
<div>
<Formik
initialValues={initialValues}
onSubmit={getSubmitHandler()}
>
{(formik) => (
<Form>
<label htmlFor="firstName">First Name</label>
<Field id="firstName" name="firstName" placeholder="First Name" />
<button
type="button"
onClick={(event) =>
getSubmitHandler()(formik.values, formik)
}
>
Submit
</button>
</Form>
)}
</Formik>
</div>
);
}

Redirection Not Working Because of Order of Executions of Functions

I have a login page. If login is successful and token is present in local storage, I want to redirect to a private page /panel. I am calling all functions on the onSubmit() of my form.
Here's what my code looks like:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
}
}
function FormSubmitted(){
setSubmitted(true);
console.log('Form submitted');
}
function RedirectionToPanel(){
console.log('ha');
if(submitted && localStorage.getItem('token')){
console.log('FInall');
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}>
{submitted && <Redirect to='/panel'/>}
{(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>
</form>
)
}}
</Formik>
</div>
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
When I hit the submit button, I check the console for what happens inside the RedirectionToPanel() function. The first time, 'Ha' is printed but when I click on it for the second time, both 'Ha' & 'Finally' are printed. However, the redirection still doesn't happen.
If I use {submitted && <Redirect to='/panel'/>}after Mutation, I get this error on mutation:
This JSX tag's 'children' prop expects a single child of type '(mutateFunction: MutationFunction<any, Record<string, any>>, result: MutationResult<any>) => Element | null', but multiple children were provided.ts(2746)
If I use it after the return and before mutation, I get syntax errors on && and }.
As #wxker stated, you need to return <Redirect> elements in the render method in order for the redirect to actually occur. E.g. https://codesandbox.io/s/proud-rgb-d0g8e.
This is happening because submitForm and FormSubmitted both contain asynchronous operations. This means that calling them one after another followed by RedirectionToPanel does not guarantee that they execute in that order. Consider using the useEffect hook to do the redirect once the token is set.
Also, the reason why the page is not redirecting is because you are not returning the Redirect component into the DOM tree. You can fix this by inserting it with a conditional statement that checks the submitted state
//when localStorage.getItem('token') changes, execute the callback
useEffect(() => {
setSubmitted(true);
}, [localStorage.getItem('token')])
...
return (
...
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);}}>
...
//This line can be anywhere in the return. Even at the end is fine
{submitted && <Redirect to='/panel'/>}
<Container />
);
If you want to do this without useEffect, you can use setSubmitted within submitForm. But even then, you must have {submitted && <Redirect to='/panel'/>} somewhere in your DOM
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);
setSubmitted(true);
})
.catch(console.log)
}
}
return (
...
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);}}>
...
//This line can be anywhere in the return. Even at the end is fine
{submitted && <Redirect to='/panel'/>}
<Container />
);

PrivateRouting when Token in Local Storage [TypeScript]

If my login in successful, an authentication token is returned, which is stored in the local storage. Upon successful login, I want to go the a private route.
I found this code Javascript snippet but I am unable to make it work for Typescript. I don't have any isAuthenthicated property yet. How could I modify this accordingly?
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{pathname: '/login', state: { from: props.location }
}}/>
)
)}/>
)
Here is my login screen:
const LoginMutation = gql`
mutation LoginMutation($email: String!, $password: String!) {
loginEmail(email: $email, password: $password)
}
`;
const schema = Yup.object({
email: Yup
.string()
.email('Invalid Email')
.required('Please Enter your Email'),
password: Yup
.string()
.required('Please Enter your password')
});
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
loggedIn: false,
});
function submitForm(LoginMutation: any) {
const { email, password } = state;
console.log(email, password)
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<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")}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
>
Submit</Button>
</form>
)
}}
</Formik>
</div>
)
}
</Mutation>
);
}
export default LoginPage;
There is a similar question but it doesn't answer my case since I'm storing the token in local storage.
just replace fakeAuth.isAuthenticated by your saved token which you might save it also as a global state right? so, in general, it just a boolean prop to check if the user is successfully login or not depend on that situation the user will redirect either to the login page or the protected page
This will go in your index.tsx file:
const token = localStorage.getItem('token');
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/login'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
And use this in the browser router/switch:
<PrivateRoute
path='/panel'
isAuthenticated={token}
component={PrivateContainer}
/>

how to handle backend errors in js frontend inside of object and with out object

i have user login and i have 2 errors handlers
email & password empty
email & password not match with data base
through post man i send
empty user name and password result is
{
"errors": {
"email": "please enter valid emails",
"password": "please enter password"
}
}
email and password wrong the result is
{
"general": "email or password not match"
}
i notice 1st error its have object errors and 2nd not have
my react js code is
// i remove css and imports
class login extends Component {
constructor() {
super();
this.state = {
email: '',
password: '',
loading: false,
errors: {}
}
}
handleChnage = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
handleSubmit = (event) => {
// console.log('hi');
event.preventDefault();
this.setState({
loading: true
});
const userData = {
email: this.state.email,
password: this.state.password
}
axios.post('/login', userData)
.then((res) => {
//console.log(res.data)
this.setState({
loading: false
});
this.props.history.push('/');
})
.catch((err) => {
console.log(err.response.data)
// let errors ={email:'',password:''}
this.setState({
//errors11: err.response.data.errors,
errors : err.response.data.errors,
// error2:err.response.data,
loading: false
})
})
}
render() {
const { classes } = this.props;
const { errors, loading,} = this.state;
return (
<Grid container className={classes.form}>
<Grid item sm />
<Grid item sm >
<img src={AppIcon} alt="app cion" className={classes.image} />
<Typography variant="h2" className={classes.pagetitle}>Login</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField id="email" name="email" type="email" label="Email" className={classes.textfeild}
helperText={errors.email} error={errors.email ? true : false} value={this.state.email} onChange={this.handleChnage} fullWidth />
<TextField id="password" name="password" type="password" label="Password" className={classes.textfeild}
helperText={errors.password} error={errors.password ? true : false} value={this.state.password} onChange={this.handleChnage} fullWidth />
{errors.general &&(
<Typography variant="body2" className={classes.customerror}>
{errors.general}
</Typography>
)}
<Button type="submit" variant="contained" color="primary" className={classes.button}>Login </Button>
</form>
</Grid>
<Grid item sm />
</Grid>
)
}
}
login.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styles)(login);
my problem is in this code if i send email and password wrong
93 | helperText={errors.email} error={errors.email ? true : false} value={this.state.email} onChange={this.handleChnage} fullWidth
this line i got error and this is my console log
general: "email or password not match"
how can i handle this kind of errors ?
Looking at the code of your request handling and the responses I can see that error responses are not consistent in how it handles errors. I think you should think about fixing the response to be consistent. I would try to replace line in error handling of submit
errors : err.response.data.errors,
with something that would create structure for general erros: f.e.
errors : {...err.response.data.errors, general: err.response.data.general}

Categories

Resources