I'm trying to make an API call in React Native to handle user authentication. I'm passing this.state.email and this.state.password (both bound to text inputs) to a function in my services/ folder, where the call is made. Afterwards, the screen should navigate to the home stack or return an error. This is my login screen code:
import AuthService from ...
export default class LoginScreen extends Component {
constructor(props) {
super(props);
this.login = this.login.bind(this);
this.state = {
email: '',
password: '',
};
}
login() {
AuthService.login(this.state.email, this.state.password)
.then(this.props.navigation.navigate('Main')) //Move to home screen
.catch(console.error('Error')); //Handle error
}
render() {
return (
<View>
<TextInput
onChangeText={email => this.setState({ email })}
value={this.state.email}
/>
<TextInput
onChangeText={password => this.setState({ password })}
value={this.state.password}
/>
<TouchableOpacity onPress={this.login}>
<Text style={styles.text}>Submit</Text>
</TouchableOpacity>
</View>
);
}
}
And this is my AuthService function:
login(email, password) {
// Check if credentials exist
if (!email || !password) {
return undefined;
}
return fetch(
`${AUTH_ROOT}/login`,
{
method: 'POST',
body: { email, password },
}
.then(
res => {
const data = { ...res.data };
if (!data) {
throw new Error('No user returned from auth service');
}
},
res => {
context.error = 'Could not login';
console.log(res);
}
)
);
},
But I'm getting the error:
> undefined is not a function (near '...{
> method: 'POST',
> body: {
> email: email,
> password: password,
> } }.then...')
What does this mean? Is my call not done correctly?
login(email, password) {
// Check if credentials exist
if (!email || !password) {
return undefined;
}
return fetch(
`${AUTH_ROOT}/login`,
{
method: 'POST',
body: { email, password },
}
)
.then(
res => {
const data = { ...res.data };
if (!data) {
throw new Error('No user returned from auth service');
}
},
res => {
context.error = 'Could not login';
console.log(res);
}
)
},
The right syntax for fetch is
fetch(url, params).then()
instead you had
fetch(url, params.then())
Related
As a beginner to the React Js I'm trying simple projects for hands-on experience and to learn as well! so, in the same way I've created one login portal with react js and WAMP server by watching YouTube tutorial
in the middle I went through some errors but somehow I managed and get them off but after completing the project when I'm trying to login with the credentials which I've given in database the response and all was clear but the page doesn't redirect to welcome page after clicking on login button instead it redirects to welcome page after refreshing manually. I couldn't identify the mistake in my code.
LoginForm.js
import React from 'react';
import InputField from './InputField';
import SubmitButton from './SubmitButton';
import UserStore from './stores/UserStore';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
buttonDisabled: false
}
}
setInputValue(property, val) {
val = val.trim();
if (val.length > 12) {
return;
}
this.setState({
[property]: val
})
}
resetForm() {
this.setState({
username: "",
password: "",
buttonDisabled: false
})
}
async doLogin() {
if (!this.state.username) {
return;
}
if (!this.state.password) {
return;
}
this.setState({
buttonDisabled: true
})
try {
let res = await fetch('/login', {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
});
let result = await res.json();
if (result && result.succes) {
UserStore.isLoggedIn = true;
UserStore.username = result.username;
}
else if (result && result.succes === false) {
this.resetForm();
alert(result.msg);
}
} catch (e) {
console.log(e);
this.resetForm();
}
}
render() {
return (
<div className="loginForm">
Log In
<InputField
type='text'
placeholder='Username'
value={this.state.username ? this.state.username : ''}
onChange={(val) => this.setInputValue('username', val)}
/>
<InputField
type='password'
placeholder='Password'
value={this.state.password ? this.state.password : ''}
onChange={(val) => this.setInputValue('password', val)}
/>
<SubmitButton
text='Log In'
disabled={this.state.buttonDisabled}
onClick={() => this.doLogin()}
/>
</div>
);
}
}
export default LoginForm;
SubmitButton.js
import React from 'react';
class SubmitButton extends React.Component {
render() {
return (
<div className="submitButton">
<button
className='btn'
type='submit'
disabled={this.props.disabled}
onClick={() => this.props.onClick()}
>
{this.props.text}
</button>
</div>
);
}
}
export default SubmitButton;
App.js
import React from 'react';
import { observer } from 'mobx-react';
import UserStore from './stores/UserStore';
import LoginForm from './LoginForm';
import SubmitButton from './SubmitButton';
import './App.css';
class App extends React.Component {
async componentDidMount() {
try {
let res = await fetch('/isLoggedIn', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
let result = await res.json();
if (result && result.success) {
UserStore.loading = false;
UserStore.isLoggedIn = true;
UserStore.username = result.username;
}
else {
UserStore.loading = false;
UserStore.isLoggedIn = false;
}
}
catch (e) {
UserStore.loading = false;
UserStore.isLoggedIn = false;
}
}
async doLogout() {
try {
let res = await fetch('/logout', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json'
}
});
let result = await res.json();
if (result && result.success) {
UserStore.isLoggedIn = false;
UserStore.username = '';
}
}
catch (e) {
console.log(e);
}
}
render() {
if (UserStore.loading) {
return (
<div className="app">
<div className='container'>
Loading ,Please Wait...
</div>
</div>
);
}
else {
if (UserStore.isLoggedIn) {
return (
<div className="app">
<div className='container'>
Welcome {UserStore.username}
<SubmitButton
text={'Log Out'}
disabled={false}
onClick={() => this.doLogout()}
/>
</div>
</div>
);
}
return (
<div className="app">
<div className='container'>
<LoginForm />
</div>
</div>
);
}
}
}
export default observer(App);
Router.js
const bcrypt = require('bcrypt');
class Router {
constructor(app, db) {
this.login(app, db);
this.logout(app, db);
this.isLoggedIn(app, db);
}
login(app, db) {
app.post('/login', (req, res) => {
let username = req.body.username;
let password = req.body.password;
username = username.toLowerCase();
if (username.length > 12 || password.length > 12) {
res.json({
success: false,
msg: 'An error occured,please try again'
})
return;
}
let cols = [username];
db.query('SELECT * FROM user WHERE username =? LIMIT 1', cols, (err, data, fields) => {
if (err) {
res.json({
success: false,
msg: 'An error occured,please try again'
});
return;
}
if (data && data.length === 1) {
bcrypt.compare(password, data[0].password, (bcryptErr, verified) => {
if (verified) {
req.session.userID = data[0].id;
res.json({
success: true,
username: data[0].username
})
return;
}
else {
res.json({
success: false,
msg: 'Invalid Password'
})
}
});
} else {
res.json({
success: false,
msg: 'User not Found,please try again..!'
})
}
});
});
}
logout(app, db) {
app.post('/logout',(req,res)=>{
if(req.session.userID){
req.session.destroy();
res.json({
success:true
})
return true;
}
else{
res.json({
success:false
})
return false;
}
})
}
isLoggedIn(app, db) {
app.post('/isLoggedIn',(req,res)=>{
if(req.session.userID){
let cols =[req.session.userID];
db.query('SELECT * FROM user WHERE ID=? LIMIT 1',cols,(err,data,fields) =>{
if(data && data.length ===1){
res.json({
success:true,
username:data[0].username
});
return true;
}
else{
res.json({
success:false
});
}
});
}
else{
res.json({
success:false
})
}
});
}
}
module.exports = Router;
you have to change some logic in LoginForm.js. There are multiple ways in which you can do that. I am showing you one example.
import React from 'react';
import InputField from './InputField';
import SubmitButton from './SubmitButton';
import UserStore from './stores/UserStore';
import {Redirect} from 'react-router-dom' //add a redirect from react-router-dom
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
buttonDisabled: false,
redirectToRefer : false //add a state here for redirect
}
}
setInputValue(property, val) {
val = val.trim();
if (val.length > 12) {
return;
}
this.setState({
[property]: val
})
}
resetForm() {
this.setState({
username: "",
password: "",
buttonDisabled: false
})
}
async doLogin() {
if (!this.state.username) {
return;
}
if (!this.state.password) {
return;
}
this.setState({
buttonDisabled: true
})
try {
let res = await fetch('/login', {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
});
let result = await res.json();
if (result && result.succes) {
UserStore.isLoggedIn = true;
UserStore.username = result.username;
this.setState({redirectToRefer:true})//set rediectToRefer as true
}
else if (result && result.succes === false) {
this.resetForm();
alert(result.msg);
}
} catch (e) {
console.log(e);
this.resetForm();
}
}
render() {
//add a condition at render if redirectToRefer is true then redirect to your page
if(redirectToRefer)
{
return <Redirect to ="/"/>
}
return (
<div className="loginForm">
Log In
<InputField
type='text'
placeholder='Username'
value={this.state.username ? this.state.username : ''}
onChange={(val) => this.setInputValue('username', val)}
/>
<InputField
type='password'
placeholder='Password'
value={this.state.password ? this.state.password : ''}
onChange={(val) => this.setInputValue('password', val)}
/>
<SubmitButton
text='Log In'
disabled={this.state.buttonDisabled}
onClick={() => this.doLogin()}
/>
</div>
);
}
}
export default LoginForm;
App.js
import { Component } from "react";
import "./App.css";
import checkUser from "./functions";
class App extends Component {
constructor() {
super();
console.log(checkUser('',''));
}
render() {
return (
<>
<h1 id="name">{this.user}</h1>
</>
);
}
}
export default App;
functions.js
let checkUser = (username, password) => {
let local_username = localStorage.getItem("username");
let local_token = localStorage.getItem("token");
let local_client_id = localStorage.getItem("client_id");
if (username === "" && password === "") {
fetch("/login?user=auth", {
method: "POST",
headers: {
username: local_username,
token: local_token,
client_id: local_client_id,
},
})
.then((data) => {
data.json();
})
.then((data) => {
return data
});
} else if (
username !== null &&
password !== null &&
local_client_id === null
) {
let res = fetch("/login?user=notloggedin", {
method: "POST",
headers: { username: username, password: password },
})
.then((res) => res.json())
.then((data) => {
if (data.status === true) {
localStorage.setItem("username", username);
localStorage.setItem("token", data.token);
localStorage.setItem("client_id", data.client_id);
return true;
} else {
return false;
}
});
}
};
export default checkUser;
I am expecting checkUser function to log user name but it is logging undefined.
It shows to request to server but not returning, I think that the problem is with the code in functions.js where I am returning something in a .then funtion.
In the first .then() you forget to return data.json(). you can either return:
.then((data) => {
return data.json();
})
//or wrap it in brackets so it automatically returns:
.then((data) => (
data.json();
))
//or make it one line:
.then((data) => data.json())
I am making a React application with the backend of the Django REST framework. Everything works fine except when retrieving the username. Retrieving the username is no problem but keeping it for a time is a problem. I'm going to explain using comments now in the code;
import React, { Component } from 'react';
import Nav from './Components/Navbar';
import LoginForm from './Components/LoginForm';
import SignupForm from './Components/SignupForm';
import Layout from './Containers/Layout';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
displayed_form: '',
logged_in: localStorage.getItem('token') ? true : false,
username: '', // username state
error: null
};
}
componentDidMount() {
if (this.state.logged_in) {
fetch('http://localhost:8000/core/current_user/', { // fetch is used to get current user
headers: {
Authorization: `JWT ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(json => {
this.setState({ username: json.username }); // set the state
});
}
}
handle_login = (e, data) => {
e.preventDefault();
fetch('http://localhost:8000/token-auth/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(json => {
try {
localStorage.setItem('token', json.token);
this.setState({
logged_in: true,
displayed_form: '',
username: json.user.username,
error: '',
})
} catch (error) {
this.setState({error: "We Couldn't log you in, maybe theres a typo in the data you entered"})
this.handle_logout()
}
});
};
handle_signup = (e, data) => {
e.preventDefault();
fetch('http://localhost:8000/core/users/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(json => {
try {
localStorage.setItem('token', json.token);
this.setState({
logged_in: true,
displayed_form: '',
username: json.username,
})
if (this.state.username == 'A user with that username already exists.') {
this.setState({logged_in: false, username: '', error: 'Yikes, we could not create your account, you have used an existing username. Try again please...'})
this.handle_logout()
}
} catch (error) {
alert("Error")
}
});
};
handle_logout = () => {
localStorage.removeItem('token');
this.setState({ logged_in: false, username: '' });
};
display_form = form => {
this.setState({
displayed_form: form
});
};
render() {
let form;
switch (this.state.displayed_form) {
case 'login':
form = <LoginForm handle_login={this.handle_login} />;
break;
case 'signup':
form = <SignupForm handle_signup={this.handle_signup} />;
break;
default:
{
this.state.logged_in ?
form = null
:
form = <LoginForm handle_login={this.handle_login} />
}
}
return (
<div className="App">
<Nav
logged_in={this.state.logged_in}
display_form={this.display_form}
handle_logout={this.handle_logout}
/>
{form}
<h3>
{
this.state.logged_in && this.state.username !== undefined ?
<Layout username={this.state.username} />
: this.state.error ?
<p className="error">{this.state.error}</p>
: this.state.username == undefined ?
<div>
<p className="error" style={{maxWidth: 650, minHeight: 30, padding: 20}}>You exceeded the time being logged in, we take your account safety seriously. So please logout now and login again...</p>
{this.handle_logout}
</div>
:
<p></p>
}
</h3>
</div>
);
}
}
export default App;
If you read through everything, I have said that this.state.logged_in && this.state.username !== undefined ? show the layout component.
In that component, I have put an h1 with {this.state.username}.
The username gets shown but it becomes null/undefined after about 5 minutes. Even though, in the backend Django REST API, the current username can be seen.
I am very confused about why this is happening.
Update
I fixed this by using local storage to set a username and get the username. But, I want to know if this is safe.
I have a Login component, which has form with inputs for user's data, and I have a method onFormSubmit with fetch. The problem is that I have no idea, how to pass token to another component ( it is there, I can console.log it ). The reason why I want to pass token to another component is that the other component is validating if user has logged in and by detecting token ( null = user didn't log in and redirect him to login page, otherwise go to protected pages )
My login.js component
class Login extends React.Component() {
constructor() {
super();
this.state = {
email: '',
password: '',
};
}
onInputChange = (e) => {
this.setState({
[e.target.id]: e.target.value
});
};
onFormSubmit = (e) => {
e.preventDefault();
fetch('http://localhost:3001/user/login', {
method: 'POST',
body: JSON.stringify({
email: this.state.email,
password: this.state.password
}),
headers: {
'Content-Type': 'application/json'
}
})
.then( res => {
if( res.status === 200){
this.props.history.push('/MyPlaces');
} else {
const error = new Error(res.error);
throw error;
}
})
.catch(err => {
console.error(err);
alert('Error login in please try again!');
});
};
render() {
return (
<div className="loginPanel">
<form onSubmit={this.onFormSubmit}>
<label>Email
<input type="text"
id="email"
value={this.state.email}
onChange={this.onInputChange}
/>
</label>
<label>Password
<input type="text"
id="password"
value={this.state.password}
onChange={this.onInputChange}/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
};
}
export default Login;
My authentication component
import React, {Component} from 'react';
import { Redirect } from 'react-router-dom';
export default function withAuth(ComponentToProtect, props) {
return class extends Component {
constructor() {
super();
this.state = {
loading: true,
redirect: false
};
}
componentDidMount() {
fetch('')
.then(res => {
if( res.status === 200) {
this.setState({ loading: false});
} else {
const error = new Error(res.error);
throw error;
}
})
.catch(err => {
console.error(err);
this.setState({ loading: false, redirect: true });
})
}
render() {
const { loading, redirect } = this.state;
if( loading ){
return null;
}
if( redirect ){
return <Redirect to="/Login" />;
}
return (
<React.Fragment>
<ComponentToProtect {...this.props}/>
</React.Fragment>
);
}
}
}
I know that there is nothing in fetch in authentication component, I thought that I should've make another api request ( same as in login component, then after fetch just invoke
.then(res => res.json()).then(res => {
let token = res.token;
console.log("token: ", token);
});
but it just doesn't seem to be good idea I think. Could you please give me some guide how may I do that?
You can simply use localStorage.setItem('token-name', token); to store the token after fetch. Then you can retrieve it using localStorage.getItem('token-name') across all the component. If the getItem returns null then you can simply redirect to login.
For logout, you can simply update the token value to null.
Login Component
fetch('http://localhost:3001/user/login', {
method: 'POST',
body: JSON.stringify({
email: this.state.email,
password: this.state.password
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => {
if( res.status === 200){
localStorage.setItem('token', res.token); //storing the token in localStorage.
this.props.history.push('/MyPlaces');
} else {
const error = new Error(res.error);
throw error;
}
})
.catch(err => {
console.error(err);
alert('Error login in please try again!');
});
Authentication Component
componentDidMount() {
let token = localStorage.getItem('token'); //retriving the token from localStorage
if(token === null){
this.setState({ loading: false, redirect: true });
}
}
Hope this will help you.
I'm trying to add toast notifications to my app, one plugin I've been trying to use is react-toastify.
The issue I'm having is probably more a general react/redux issue more than with a plugin such as react-toastify.
I'm using a reducer to set the redux state for errors and success messages, from what I understand with the current code, each error or success message is persistent in the store until another action is called to clear them.
The issue I can't figure out is how do I trigger a toast only once. Eg. I enter the wrong credentials, it creates an error toast, but whenever the state changes and reloads (typing anything into the email or password fields) it creates another toast.
How do I get it to only show once?
userActions.js
function handleErrors(res) {
if (res.ok) {
return res.json();
} else {
return res.json().then(err => {throw err;});
}
}
export const login = (user) => dispatch => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res =>
dispatch({
type: LOGIN,
payload: res
})
)
.catch(error =>
dispatch({
type: ERROR,
payload: error
})
)
}
userReducer.js
const initialState = {
errors: '',
success: ''
};
export default function(state = initialState, action) {
switch (action.type) {
case LOGIN:
return {
...state,
errors: '',
success: action.payload.message
};
case ERROR:
return {
...state,
success: '',
errors: action.payload.message
}
default:
return state;
}
}
app.js
app.post('/login', function(req, res) {
... return res.status(500).send({ message: 'Wrong credentials' });
... return res.status(200).send({ message: 'good!' });
});
login.js
class Login extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
}
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
render() {
const { errors, login, success } = this.props;
if (success !== '') toast.success(success, {
position: toast.POSITION.TOP_CENTER
});
if (errors !== '') toast.error(errors, {
position: toast.POSITION.TOP_CENTER
});
return (
<div>
<input type="text" id="email" placeholder="Email Address" onChange={this.handleChange} />
<input type="password" id="password" placeholder="Password" onChange={this.handleChange} />
<button onClick={() => login(JSON.stringify({email: this.state.email, password: this.state.password}))}>Log In</button>
<ToastContainer />
</div>
)
}
}
const mapStateToProps = state => ({
errors: state.store.errors,
success: state.store.success
});
export default connect(mapStateToProps, {login})(Login);
You're calling toast.success or toast.error inside render which makes a new toast pop up every time you re-render the component.
The solution is simple. Move your toast calls outside render, where they will only be called once.
One way to achieve this is to return a value from your userAction.
export const login = (user) => dispatch => {
return new Promise((resolve, reject) => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res => {
dispatch({
type: LOGIN,
payload: res
})
resolve(res)
}
)
.catch(error => {
dispatch({
type: ERROR,
payload: error
})
reject(error)
}
)
}
}
Then use that value to toast in login.js.
class Login ... {
...
loginUser = () => {
this.props.login(JSON.stringify({email: this.state.email, password: this.state.password}))
.then(res => {
toast.success(res.message, { position: toast.POSITION.TOP_CENTER })
}
).catch(error => {
toast.error(error.message, { position: toast.POSITION.TOP_CENTER })
}
)
}
...
render() {
return (
...
<button onClick={this.loginUser}>Log In</button>
...
)
}
}
There are other ways to achieve the same functionality and depending on the structure of your project, you may want to toast in a more generalized way.