React. Why this.state does not updates correctly? - javascript

So, I try to run the function by condition: if I got an Error in the catch method.
To do this, I implement the this.state.loginError in component state, that will change on true if we got an Error. So, and after error - the this.state.loginError comese back with true (and this is also I saw in console.log), but after state changes - my function loginError(target) does not want to start anyway.
See my code and logs below, please:
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
loginError: false,
}
}
handleSubmit = (e) => {
e.preventDefault();
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
this.setState({
loginError: true
});
console.log(this.state.loginError); // gives `true`
});
if (this.state.loginError) {
console.log('Error!') //does not work
loginError(target);
}
};

Because axios.post is asyc function, and at first fires your if condition, and after .catch hook. For fix this try to replace your condition in other place, for example in componentDidUpdate method.
componentDidUpdate() {
if (this.state.loginError) {
console.log('Error!') //does not work
loginError(target);
this.setState({ loginError: false });
}
}

Check this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_functio
You are basically trying to check the state when the error is still not caught, and hence the state has not changed.
If you move your code to the render method you will see that it will works since it will re-render after the state changes. Or you get the state change withing the componentDidUpdate method.

Why don't you try Promises instead, which is very clear and simple way around.
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
loginError: false,
}
}
handleSubmit = (e) => {
e.preventDefault();
return new Promise(resolve, reject){
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
reject(err);
})
.then(result => resolve(result));
}
//Else where in Your component use this promise, where ever you call it..
handleSubmit().then(// success code).error(// error code)
};

Because axios.post returns a promise, all code you'll write after it will be executed before the .then() or .catch() statements. If your need is to call loginError() function when the request fails you can call it in .catch statement :
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
loginError(target);
});
If you need your function to be executed after updating the state you can use the setState callback (second argument) :
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
this.setState({ loginError: true }, () => { loginError(target); })
});

Related

State of array is not updated in ComponentDidMount

I have a weird situation, where I have an array as a state:
this.state = { ActivityItem: []} and I am pushing values to it from library that calls an API like this:
getDataFromKit(dateFrom) {
Kit.getSamples(stepCountSample, (err, results) => { //api call
if (err) {
return;
}
const newData = results.map(item => { return { ...item, name: 'itemAmount' } });
this.setState({ d: [...this.state.ActivityItem, ...newData] })
})
Then, I call this method from ComponentDidMount() for array to be loaded
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
console.log("values are", this.state.ActivityItem)
}
Now, the weirdest part: somehow the array is empty in ComponentDidMount, but when I display elements of Array in return of render() function it displays all the values that were added correctly. How is that possible and how I might fix that?
setState is asynchronous in nature. Therefore, logging the state just immediately after setting it can give this behaviour but if set properly, it will display the required content which is happening in your case. Also componentDidMount is called only once in the beginning so you can check for logs in componentDidUpdate method.
State updates are async in nature. If you want to print the state soon after setting your state in class component, then pass a function to the 2nd argument of setState.
Like this
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
// remove console.log
}
...
getDataFromKit(dateFrom) {
...
this.setState({ ActivityItem: [...this.state.ActivityItem, ...newData] }), () => {
console.log("values are", this.state.ActivityItem) //<----
}
})
...
}
use prevstate while updating the state value. React setState is an asynchronous update and does batch updating. Using prevState makes sure that the state value is updated before calculating new state value.
getDataFromKit(dateFrom) {
let stepCountSample = {
startDate: dateFrom.toISOString(),
type: "Type1"
};
Kit.getSamples(stepCountSample, (err, results) => {
//api call
if (err) {
return;
}
const newData = results.map(item => {
return { ...item, name: "itemAmount" };
});
this.setState(prevState => {
ActivityItem: [...prevState.ActivityItem, ...newData];
});
});
}
DOCUMENTATION would help understand the concept
Also, console.log would directly not give the updated state, since state updates are batched. You can use a callback method to setState function. The callback will run only after successfull updation of state value

Why am I getting 2 times data null and data?

I am getting two time data null and data, what is my problem? And, why should I write two time data? Is it problem with json? Can anybody help me?
Contex.js
class ProviderWrapper extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
isLoading: false
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(URL + JSON_PATH)
.then(response => response.json())
.then(data => this.setState({ data, isLoading: false }));
}
render() {
const { children } = this.props;
return <Context.Provider value={this.state}>{children}</Context.Provider>;
}
}
test.js
import React, { Component } from "react";
import { Ctx } from "../Context/Context";
class Menu extends Component {
static contextType = Ctx;
render() {
const { data } = this.context;
console.log("data",data)
return (
<MenuWrapper>
{data && data.name}
</MenuWrapper>
);
}
}
data in ProviderWrapper starts out null and you don't start the fetch until componentDidMount, so data will be null for at least one call to render. You haven't shown what Menu and ProviderWrapper are both in, but Menu's render will be called whenever it needs to render, regardless of whether the fetch is done. It's not at all surprising that it does that at least once, and twice doesn't seem odd either.
Menu needs to be able to handle it when data is null (which it already seems to, so that's good).
A couple of side notes:
It's not the problem, but you're falling prey to a footgun in the fetch API: You need to check ok before calling json, details on my anemic little blog.
You're not handling errors at all. If the fetch fails for whatever reason, your ProviderWrapper is just left in the loading state forever. You need to handle errors.
Here's what that fetch call should look like:
fetch(URL + JSON_PATH)
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
response.json();
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => {
// ...handle/show error here and clear the loading state...
});
(In my projects, I have a wrapper for fetch so I don't have to do that every time. Making HTTP errors fulfillments rather than rejections was a major mistake in the API.)

React-Native async Array.map = undefined is not an Object

I try to create my first hybrid App with ReactNative. I have an issue with my Array.map…
class HomeScreen extends React.Component {
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: {}
};
async getPokemonFromApiAsync() {
try {
this.setState({isLoading: true});
let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
return this.setState({
isLoading: false,
data: await response.json()
});
} catch (error) {
console.error(error);
}
(...)
componentWillMount() {
this.getPokemonFromApiAsync()
}
(...)
result = (result = this.state.data.results) => {
console.log('test', this.state.data);
return (
<View>
(...)
result.map( (item, index) => {
(...)
}
</View>
)
}
}
I don't understand, why my function getPokemonFromApiAsync is empty. iOS Simulator returns a TypeError: Undefined is not an object (evaluating 'result.map')
And when adding a constructor like:
constructor(props) {
super(props)
this.getPokemonFromApiAsync = This.getPokemonFromApiAsync.bind(this)
}
I have an many errors in console:
Warning: Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the %s component., setState, HomeScreen
For me, it's normal…
What is a good lifecycle for an asynchronous Http request?
Best way using axios library github link
npm install axios
Finally, weekly downloads are more than 4,000,000+ Github Starts 50,000+
Your error is caused by how you have set up your initial data in state.
You have set it up as:
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: {} // <- here you define it as an object, with no parameters
};
You should be setting it as an object with a results parameter`. So your initial state should look like
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: { results: [] } // <- here you should define the results inside the object
};
The reason you are getting the error:
TypeError: Undefined is not an object (evaluating 'result.map')
Is because on the initial render, before your fetch response has come back, it is trying to map over this.state.data.results which doesn't exist. You need to make sure that there is an initial value for results in the state.
That should stop the initial error, however you will have to make sure that what you are saving into state for data is also an array, otherwise you will continue to get the same error.
componentWillMount has been deprecated and you should be using componentDidMount.
Also as you are calling an async function inside you componentWillMount you should refactor it in the following way:
async componentDidMount() {
await this.getPokemonFromApiAsync()
}
So that the mounting doesn't occur until the fetch request has been completed.
I would also refactor your getPokemonFromApiAsync so that you get the response.json() before trying to set it into state. You also don't need the return statement as this.setState doesn't return anything.
async getPokemonFromApiAsync() {
try {
this.setState({isLoading: true});
let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
let data = await response.json(); // get the data
this.setState({
isLoading: false,
data: data // now set it to state
});
} catch (error) {
console.error(error);
}
Snack:
Here is a very simple snack showing the code working https://snack.expo.io/#andypandy/pokemon-fetch
Code for snack:
export default class App extends React.Component {
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: { results: [] } // <- here you should define the results inside the object
};
getPokemonFromApiAsync = async () => {
try {
this.setState({ isLoading: true });
let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
let data = await response.json(); // get the data
this.setState({
isLoading: false,
data: data // now set it to state
});
} catch (error) {
console.error(error);
}
}
async componentDidMount () {
await this.getPokemonFromApiAsync();
}
render () {
return (
<View style={styles.container}>
{this.state.data.results.map(item => <Text>{item.name}</Text>)}
</View>
);
}
}
A better way is to implement your state values when your promise is resolved using "then".
let response = fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20')
.then((response) => {
this.setState({
isLoading: false,
data: response.json()
});
});
Maybe you can process your data (result.map) in the Promise callback and directly insert the result in your component.
And by the way, XHR calls are generally processed in the componentDidMount method.
The reason you are getting the error TypeError: Undefined is not an object (evaluating 'result.map') is that you are getting the result from this.state.data.results, but because data is async, at the first time it renders, data is {} (because you set it in the state), so data.result is undefined and you can't use .map() in a undefined.
To solve this, you can check if data.result is not undefined, before render it.
return (
<View>
(...)
result && result.map( (item, index) => {
(...)
}
</View>
)

Am i promise chaining correctly?

onSubmit(e) {
e.preventDefault();
const user = {
fname: this.state.firstname,
lname: this.state.lastname,
email: this.state.email,
username: this.state.username,
password: this.state.password
}
new Promise((resolve,reject) => {
this.props.fetchUser(this.state.username)
.then(res => {
this.setState({failed: this.props.exists})
if(!this.state.failed)
this.props.registerUser(user)
})
.then(res => {
this.setState({registered: this.props.status});
resolve();
})
})
}
This is my attempt at chaining promises. The idea was that registered should update correctly to the state of this.props.status (true/false).
When this.props.registerUser is called in the first promise, it changes this.props.status to true. However, registered is being set to false (which is the value of this.props.status before registerUser is called), rather than true.
I know for sure that this.props.status is changing to true, but the state of registered isn't changing.
I'm new to this stuff.
I'm assuming that fetchUser and registerUser are functions that return promises. In that case, you do not need to wrap the call for fetchUser in a new Promise(...) since it will return a promise when invoked.
The reason that the second then(...) is not being called, is that you never return a promise from the first then(...).
if(!this.state.failed)
this.props.registerUser(user)
should become
if(!this.state.failed)
return this.props.registerUser(user)
With these two modifications, your code should look like so
this.props.fetchUser(this.state.username)
.then(res => {
this.setState({
failed: this.props.exists
});
if (!this.state.failed) {
return this.props.registerUser(user)
}
})
.then(res => {
this.setState({
registered: this.props.status
});
})
Furthermore, you would expect to read the result of fetchUser(...) on the res object rather than the component props.
One final caveat that you should be aware of is that setting the state and reading it immediately after, is not guaranteed to always work as expected. The safe way to do it, is to pass a function as your second argument to setState and that will be invoked when the state is updated by React.
The simplest way to do it in this case is to avoid reading the state altogether and instead using a temporary variable.
const exists = this.props.exists;
this.setState({
failed: exists
});
if (!exists ) {
return this.props.registerUser(user)
}

Javascript/React: How to detect `await` call and setState accordingly?

I have a function foo that may or may not invoke several asynchronous requests in order to cache a token:
async foo() {
let token = this._token;
if(!token) {
token = await this.getTokenFromStorage();
}
if(Tokens.isExpired(this._token) {
this._token = await Tokens.refresh(this._token);
} else {
this._token = token;
}
return this._token;
}
Now, I want the React comoponent calling foo to have some logic like:
Call foo()
If foo has cached token, setState{isLoading: false}
Otherwise, setState{ isLoading: true }, and wait for async operation to complete. Call setState{ isLoading: false } after async op is done and returns the newly cached token...
Pretty lost as to how to go about doing this though...Help appreciated.
I am not entirely sure how you have structured your application, however perhaps the following provides you with some clues on how you can achieve loading state changes in relation to your async foo() method:
class YourReactComponent extends React.Component {
constructor(props) {
// Set the initial default state of your component.
// token added as an optional extra for the purpose of this example
this.state = {
loading : false,
token : ''
}
}
async getTokenFromStorage() {
// ... your implementation
}
// Modifed version of your foo method
async foo(token) {
// Condition to determine if loading state should be updated
// We will use this to avoid redundant setState calls
const shouldLoad = !token || Tokens.isExpired(token);
// Check to see if we need to update loading state
if(shouldLoad) {
this.setState({ loading : true })
}
if(!token) {
token = await this.getTokenFromStorage();
}
else if(Tokens.isExpired(token)) {
token = await Tokens.refresh(token);
}
// If loading state was previously updated, reset it
if(shouldLoad) {
this.setState({ loading : false })
}
return token;
}
// A render method I have created to demonstrate the concepts at hand
render() {
const { loading, token } = this.state
return (<div>
<button onClick={ async () => console.log(`Got this token: ${ this.foo(token) }`) }>load token</button>
<p>{ loading ? 'Loading' : 'Ready' }</p>
</div>)
}
}
The key concept to keep in mind are:
the setState(..) method is avalible from within React.Component instances
calling setState(..) is used to update internal state for the component
calling setState(..) also causes a re-render of the component (and affected children).
For more on the .setState() method, see the documentation

Categories