How do I properly call multiple functions in componentDidMount? - reactjs

I am trying to call two different functions to display data on my page and one is working (getUserscards) and the second one is not (getUserInfo). I saw that you can put two different function calls for the data in componentDidMount and I have tried doing it several ways but the data for the email and names is not showing up. I will include the code from my profilepage where the componentDidMount is and from my api page where the function is made.
Here is my code in profilepage. js:
import React from 'react'
import { getUsersCards, getUsersInfo } from '../Api'
class ProfilePage extends React.Component {
constructor () {
super()
this.state = {
token: window.localStorage.getItem('login_auth_token'),
username: localStorage.getItem('login_username') || '',
email: '',
first_name: '',
last_name: '',
cards: []
}
}
componentDidMount () {
if (this.state.token) {
getUsersCards(this.state.token)
.then(cards => this.setState({ cards: cards }))
}
getUsersInfo(this.state.email, this.state.first_name, this.state.last_name)
.then(data => this.setState({ data: data }))
}
componentDidUpdate (prevProps, prevState) {
if (this.state.token && this.state.token !== prevState.token) {
getUsersCards(this.state.token).then(cards => this.setState({ cards: cards }))
}
}
render () {
return (
<div>
<div className='container2'>
<div>
<p>Username: {this.state.username}</p>
</div>
<div>
Email: {this.state.email}
</div>
<div>
<p>Name: {this.state.first_name}{this.state.last_name}</p>
</div>
</div>
<div>
{this.state.cards.map(card => <p className='container' key={card.id}> Title: {card.card_name} <br /> Card: {card.card_text}</p>)}
</div>
</div>
)
}
}
export default ProfilePage
And here is the function from my api.js page:
export function getUsersInfo (token) {
return request.get('/users/info', {
headers: {
Authorization: `Token ${token}`
}
}).then(res => {
console.log(res.data)
return res.data
})
}

The thing is, when you call this.setState({ data: data }), in your state MUST HAVE a key data.
Maybe change into this may help
this.setState({ email: data.email, first_name: data.first_name, last_name: data.last_name })
Just need the exactly key to work

the problem is likely here,
export function getUsersInfo (token) {
return request.get('/users/info', {
headers: {
Authorization: `Token ${token}`
}
}).then(res => {
console.log(res.data)
return res.data //!! note
})
}
You are returning raw data from the server when are you parsing it to json, YOu Must parse it somehow to make is object so you can use.

Related

React: call parent hook which results in child not being rendered safely

I am trying to implement JWT based authentication in React. I followed this tutorial from digitalocean, but used axios (which is promise based) instead of the fetch API.
If authentication fails, the error message is displayed as required, and on a successful login, the correct page is being displayed. React throws the following error however:
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 the componentWillUnmount method.
I guess this is due to the fact that I call the loginpage parent's hook to set the JWT from within the .then() call from the axios promise, but the parent will stop rendering the loginpage and it's axios promise as soon as the jwt is set. How would I go about solving this neatly?
function App() {
const [token, setToken] = useState();
if(!token) {
return <EmployeeLogin setToken={setToken} />;
}
return (
<main>
<Switch>
<Route path="/someroute" component={someComponent} exact />
</Switch>
</main>
);
}
export default App;
// EmployeeLogin.jsx
const styles = theme => ({ ... });
class EmployeeLogin extends React.Component {
constructor(props) {
super(props);
const { setToken } = this.props;
this.state = {
email: '',
password: '',
error: null,
isLoading: false,
};
this.handleSubmitevents = this.handleSubmitevents.bind(this);
}
async handleSubmitevents(event) {
event.preventDefault();
const credentials = {
email: this.state.email,
password: this.state.password,
}
this.setState({
isLoading: true,
error: null,
});
axios.post('http://localhost:8080/account/employee/login', credentials, {
headers: {
"Content-Type": 'application/json', 'Accept': 'application/json'
}
})
.then(res => {
this.props.setToken(res.data.token); // Set the token to the parent (App.js)
})
.catch(error => {
this.setState({
error: error.response.data.message, // Show error message on failure
});
})
.then(() => {
this.setState({ isLoading: false }); // Always stop the loading indicator when done
});
}
componentWillUnmount() {
// How to terminate the promise neatly here?
}
render() {
const { classes } = this.props;
return (
<form className={classes.form} onSubmit={this.handleSubmitevents} noValidate>
<TextField
required
value={this.state.email}
onChange={(e) => { this.setState({ email: e.target.value }); }}
/>
<TextField
required
type="password"
value={this.state.password}
onChange={(e) => { this.setState({ password: e.target.value }); }}
/>
<Button type="submit">Sign In</Button>
</form>
);
}
}
EmployeeLogin.propTypes = {
classes: PropTypes.object.isRequired,
setToken: PropTypes.func.isRequired,
};
export default withStyles(styles)(EmployeeLogin);
Try to call setToken after setting isLoading to false so you can try something like below.
async handleSubmitevents(event) {
event.preventDefault();
const credentials = {
email: this.state.email,
password: this.state.password,
}
this.setState({
isLoading: true,
error: null,
});
let responseToken = ''; // set responseToken to blank before calling api
axios.post('http://localhost:8080/account/employee/login', credentials, {
headers: {
"Content-Type": 'application/json', 'Accept': 'application/json'
}
})
.then(res => {
responseToken = res.data.token; // set responseToken here
})
.catch(error => {
this.setState({
error: error.response.data.message, // Show error message on failure
});
})
.then(() => {
this.setState({ isLoading: false });
this.props.setToken(responseToken); // call props method from here
});
}

Username state gets undefined / null after a few minutes in JWT authentication React Django

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.

Passing data from fetch to another component

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.

React API returns empty

fairly new to react but I have the following API data I want to create as a list or whatever:
https://jsonblob.com/53c7f296-d79d-11e8-839a-a312b719c60a
My react component looks like this:
class Contacts extends Component {
constructor(props) {
super(props)
this.state = {
contacts:[]
}
}
componentDidMount() {
fetch('https://api.billysbilling.com/v2/contacts?organizationId=kZeGqbBRSXyeeDoWNkF3jQ',{
headers: {
'X-Access-Token': API_KEY,
'Content-Type': 'application/json'
},
})
.then(response => {
return response.json();
})
.then(response => console.log(response))
.then(d => {
this.setState({ contacts: d });
console.log("state", this.state.contacts)
})
}
render() {
return (
<div>
{
this.state.contacts.map(((contact, index) =>
<th key={`${contact.contact}${index}`}>
<div>
<div>
{contact.contact}
</div>
</div>
</th>
))
}
</div>
);
}
But however it seems to return nothing.
The console.log actually shows the data, so I am pretty stuck.
It would be awesome if some of you could help.
The state also just returns an empty array in the react tools in chrome.
When you write then(response => console.log(response)), you are not returning anything, so d will be undefined in the function given to the following then.
You could write it like this instead:
fetch('https://api.billysbilling.com/v2/contacts?organizationId=kZeGqbBRSXyeeDoWNkF3jQ',{
headers: {
'X-Access-Token': API_KEY,
'Content-Type': 'application/json'
},
})
.then(response => {
return response.json();
})
.then(d => {
console.log(d);
this.setState({ contacts: d.contacts });
});

React-Redux: How do I set the initial state from the response of an asynchronous AJAX call?

How to setState() the response received from an AJAX Request so that I can display them in the page?
constructor(props)
{
super(props);
this.state = {
email: '',
first_name: '',
middle_name: '',
country: '',
country_code: '',
mobile_number: '',
gender: ''
}
}
componentDidMount()
{
store.dispatch(getUserProfile())
.then(() => {
const user = this.props.userProfile.userProfile && this.props.userProfile.userProfile.data.data;
this.setState({
email: user.email,
first_name: user.first_name
});
})
}
render()
{
return (
<div className="form-group col-sm-12">
<label htmlFor="email">Email*</label>
<input type="email" name="email" value={this.state.email || ''}/>
</div>
<div className="form-group col-sm-12">
<label htmlFor="email">First Name*</label>
<input type="email" name="email" value={this.state.first_name || ''}/>
</div>
)
}
Apparently, I can't use .then() with store.dispatch method.
Uncaught TypeError: _store2.default.dispatch(...).then is not a function
getUserProfile() action function
import axios from 'axios';
export function getUserProfile()
{
return function(dispatch)
{
dispatch(userProfileSuccess(false));
dispatch(userProfileError(null));
const request = axios
({
url: "http://testapi/auth/v1/user/details",
method: "get",
headers: {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + localStorage.getItem('access_token')
}
})
.then(function(response) { dispatch(userProfileSuccess(response)); })
.catch(function(error) {
console.log(error)
});
return {
type: 'USER_PROFILE_SUCCESS',
payload: request
}
};
}
function userProfileSuccess(userProfile)
{
return {
type: 'USER_PROFILE_SUCCESS',
userProfile: userProfile
};
}
function userProfileError(userProfileError)
{
return {
type: 'USER_PROFILE_ERROR',
userProfileError: userProfileError
};
}
export default getUserProfile;
In the AJAX call, I tried:
.then(function(response) {
return new Promise((resolve) => {
dispatch(userProfileSuccess(response));
resolve();
});
})
but the console reports the same error.
Is there a callback that I can pass to store.dispatch? What is the correct approach to this?
You can add a callback in componentDidMount()
componentDidMount()
{
store.dispatch(getUserProfile(), () => {
const user = this.props.userProfile.userProfile && this.props.userProfile.userProfile.data.data;
this.setState({
email: user.email,
first_name: user.first_name
});
})
}
This may not run exactly same, I just want to give you an idea how to add callback using arrow function so that you don't need to use then.
As you are using redux then your redux store should keep track about when the api call is in progress or has completed or caught some error. So instead of passing any callback or promise, you should dispatch an action for each event like processing, success, error etc (which you are already doing in getprofile function). Though i would say you nicely distinguish between process, success, error. For example you getprofile method should roughly look like this
export function getUserProfile() {
return function (dispatch) {
dispatch(userProfileProcessing())
const request = axios({
url: "http://testapi/auth/v1/user/details",
method: "get",
headers: {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + localStorage.getItem('access_token'),
},
})
.then(function (response) {
dispatch(userProfileSuccess(response))
})
.catch(function (error) {
dispatch(userProfileError(response))
console.log(error)
});
};
}
It is just what i prefer. If you want it your way, that is also fine.
Now everytime you dispatch any action, redux will update the reducer state. So thats the place where you can set/reset some flag to make the component aware of what is going on with api call. So your reducer might look like this:
// getUserProfileReducer.js
userProfileReducer = (state = {}, action) => {
switch (action.type) {
case 'USER_PROFILE_PROCESSING':
return {
...state,
processing: true,
success: false,
fail: false,
userProfile: null,
}
case 'USER_PROFILE_SUCCESS':
return {
...state,
processing: false,
success: true,
fail: false,
userProfile: action.userProfile,
}
case 'USER_PROFILE_Error':
return {
...state,
processing: false,
success: false,
fail: true,
userProfile: null,
}
}
}
Now all you need to do is to access this state from you component so that you can take necessary action according to that. For that you can user mapStateToProps function which convert the redux state to prop of the component.
constructor(props) {
super(props)
this.state = {
email: '',
first_name: '',
middle_name: '',
country: '',
country_code: '',
mobile_number: '',
gender: '',
}
}
componentWillReceiveProps(newProps) {
if (newProps.userProfileStatus.success) {
// The success flag is true so set the state
const user = newProps.userProfileStatus
this.setState({
email: user.email,
first_name: user.first_name,
})
}
else if (newProps.userProfileStatus.processing) {
// Api call is in progress so do action according to that like show loader etc.
}
}
componentDidMount() {
store.dispatch(getUserProfile())
}
render() {
return (
...
)
}
const mapStateToProps = (state) => {
return {
userProfileStatus: state.userProfileReducer,
}
}
Redux stores the state in the Redux store, separately from the React component state (think setState). You are almost there. What you need to do is guide the result data from the async dispatch to the redux store and then to your local component state. Steps 3 and 4 below.
Dispatch an async action to fetch the data.
Dispatch an action from within the promise to populate the redux state.
Write a reducer that intercepts the action and populates the redux state.
Connect your local component state with the redux state by using the connect function.

Resources