ReactJS: Uncaught (in promise) this.setState is not a function - javascript

I am dealing with the following frustrating error:
Home.js:231 Uncaught (in promise) TypeError: _this9.setState is not a function. The error is coming from the last line of the following function:
checkIfRunning() {
return fetch('/api/following/iscurrentlyrunning', {
credentials: 'include',
})
.then(response => {
console.log(response.status);
if (response.status === 200) {
return response.json();
}
})
.then(response => {
let cState = this.state;
cState.running = response;
this.setState(cState);
});
}
I did bind the function in the component constructor and when I call it alone, it works fine. The issue arise when I try to invoke the function in a timer (setInterval). In componentWillMount, I call few functions:
componentWillMount() {
this.checkIfFirstTimeLogin()
.then(() => {
// user already exists
if (!this.state.firstLogin) {
this.Name();
this.getRole();
setInterval(() => this.checkIfRunning(), 10000);
}
})
.then(() => {
let cState = this.state;
cState.pageLoading = false;
this.setState(cState);
})
.catch(error => console.log(error));
}
I have the intuition that the promise chain breaks the binding for a reason I do not presently understand.
Thank you for any help,

Promises are a guaranteed future, which means the whole promise chain will fire once invoked and there's little you can do to stop it.
On a practical level, this means you need to check to be sure that your component instance is still mounted before trying to access setState off it, as the component may have unmounted before this promise chain completes.
.then(response => {
...code here...
// important! check that the instance is still mounted!
if (this.setState) {
this.setState(cState);
}
});
Also, you should never mutate local state directly as you are doing here:
// don't mutate state directly, use setState!
let cState = this.state;
cState.running = response;

You are mutating the state directly, it is not allowed, in the final example you are still doing it. It is better to use an Object.assign(…) to create new object like this :
let newState = Object.assign({}, ...this.state, running: response);
Then, only do your setState() call
this.setState(newState);
One of the fundamental principles of React is that changes to State are not done directly but with the setState function which will put the change to queue, and it will be done either alone or with batch update.

You can try change function checkIfRunning() {} to checkIfRunning = () => {} to pass this into function

Thanks all for the help, very appreciated.
I solved the problem with the following fix, although I am not sure why it works now:
checkIfRunning() {
return fetch('/api/following/iscurrentlyrunning', {
credentials: 'include',
})
.then(response => {
console.log(response.status);
if (response.status === 200) {
return response.json();
}
})
.then(response => {
let cState = this.state;
cState.running = response;
this.setState({cState});
});
}
Notice how this.setState(cState) became this.setState({cState}).
Thanks all for your time, it led to interesting research on my part.

Related

console log of returned value shows a unusable response even after chaining a then to it

Im trying to return a promise is a javascript file. However, there is a weird issue. So when I console log the returned value within the function, it shows the following:
const id = getAccounts()
.then(res => res.find(acc => acc.type === ACCOUNT_TYPES.STARTER))
.then((res) => { return res.id });
console.log(id.then(res => res))
Is there anything I am missing? Have been dealing with this and research for the whole day. If anyone can help, I would highly appreciate it!
Updated section:
const initialState = {
currentAccountId: id.then((res) => { return res; }) || ''
};
The return value of calling a Promise's .then is always another Promise. By setting currentAccountId to id.then, it will always be a Promise.
You need to call this.setState from inside the Promise's resolve function:
componentDidMount() {
getAccounts()
.then(res => res.find(acc => acc.type === ACCOUNT_TYPES.STARTER))
.then((res) => { this.setState({ currentAccountId: res }); });
}
Use componentDidMount, like the React docs suggest, to initiate an async request. "If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
Original answer
id.then will always return a new Promise, and that's what you are logging. To log the actual value you can move the console.log inside the resolve function:
id.then(res => console.log(res))

React how to call 1 promise after the previous promise is finish?

I have a React hook with this structure. What I want to do is, after finish calling getUserJoinedSlotList() and getting the result, then I want to call getAllAvailableSlot() both set the result into the useState hooks.
const [joinedSlotList, setJoinedSlotList] = useState(null)
const [availableSlotList, setAvailableSlotList] = useState(null)
const [isAllSlotLoading, setIsAllSlotLoading] = useState(true)
const getJoinedList = () => {
getUserJoinedSlotList()
.then(res => {
setIsLoading(false)
setJoinedSlotList(res.joined_slot)
})
.catch(error => {
setIsLoading(false)
setErrorMsg(error.message)
})
}
const getAvailableSlotList = () => {
getAllAvailableSlot()
.then(res => {
setIsAllSlotLoading(false) // this setting not working, at the 2nd API call
setAllAvailableSlotList(res.slot)
})
.catch(error => {
setAvailableErrMsg(error.message)
setIsAllSlotLoading(false)
})
}
useEffect(() => {
if (user !== null) {
getJoinedList()
}
}, [user])
Here is the code for getAvailableSlot(), I am using Aws amplify, so it actually return a promise for the GET request
import { API } from 'aws-amplify';
export const getAllAvailableSlot = async () => {
let path2 = path + '/list_all'
return API.get(apiName, path2)
}
What I tried:
Put in getAvailableSlotList as a callback function of getJoinedList(), like this:
const getJoinedList = (callback) => {
getUserJoinedSlotList()
.then(res => {
setIsLoading(false)
setJoinedSlotList(res.joined_slot)
})
.catch(error => {
setIsLoading(false)
setErrorMsg(error.message)
})
callback()
}
then
useEffect(() => {
if (user !== null) {
getJoinedList(getAvailableSlotList) // put in as call back here
}
}, [user])
By this, getAllAvailableSlot is called, I getting the result. But the result is not being set after calling setAvailableSlotList, and setIsAllSlotLoading(false) is not working as well, still true
Then I tried to call like this:
const getJoinedList = () => {
getUserJoinedSlotList()
.then(res => {
setIsLoading(false)
setJoinedSlotList(res.joined_slot)
getAvailableSlotList() // here call the function
})
.catch(error => {
setIsLoading(false)
setErrorMsg(error.message)
})
}
Again, is same result as above attempt.
Then I tried like this as well:
const calling = async () => {
await getJoinedList()
await getAvailableSlotList() //setAvailableSlotList and setAllIsLoading is not working, the 2ND CALL
}
useEffect(() => {
if (user !== null) {
//getJoinedList()
calling()
}
}, [user])
But still the getAvailableSlotList() set hooks is not taking any effect.
Specific problem:
I noticed that, the 2nd API calling is successful, but the follow up function which I call to setting the hooks, it just not taking any effect.
Means that:
Everything in getJoinedList() is working just fine. But when reach to getAvailableSlotList(), I can get the API result from it, but the setAvailableSlotList and setIsAllSlotLoading both cant set in the value
Question:
How to call another API after 1 API call is finished?
How to set react hooks at the 2nd API call?
Your second attempt should work. Here is a simplified sandbox example: https://codesandbox.io/s/romantic-bhaskara-odw6i?file=/src/App.js
A bit explanation on where the first and third attempts went wrong:
The first attempt is almost there, just that you need to move callback() inside the .then() block which essentially brings you to the second attempt.
The third one you used async/await but the problem is neither getJoinedList() nor getAvailableSlotList() returns a Promise so both requests will be sent around the same time without one waiting on the other to resolve first.
The simplest solution is actually to add your entire getAllAvailableSlot() function inside the getUserJoinedSlotList() through chaining. I see you're already using that, so I don't need to explain it in depth.
getUserJoinedSlotList().then(res => {
--- logic ---
getAllAvailableSlot().then(res2 => {
-- logic ---
}
}
Then chaining and its pairing could work here.
await getUserJoinedSlotList()
.then(res => /*assign hooks data*/)
.then(() => getAllAvailableSlot())
.then(availableSlot => /*assign availableSlot data*/)
.catch(e => console.log(e))

Asynchronous ActionCreator in React Redux

I'm pretty new in React-Redux. Was working on an application. The thing is that I faced some issues with asynchronous execution of Redux actionCreator, may be.
Below is my component. Say, I want to call an actionCreator from componentDidMount() or from an onclick event listener.
class Dashboard extends PureComponent {
componentDidMount() {
this.props.getProductsAndPackages();
let something = [];
something = this.props.products;
}
....................................
}
Or , the function this.props.getProductsAndPackages(); can be an onClick event handler that does the same thing, context is the same. I'll ask my question after first explaining my code.
At the lower side of my Dashboard container:
Dashboard.propTypes = {
getProductsAndPackages: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
.......................
};
const mapStateToProps = (state) => {
return {
.....................
products: state.products.products,
...................
};
};
const mapDispatchToProps = (dispatch) => {
return {
getProductsAndPackages: () => dispatch(getProductsAndPackagesActionCreator()),
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Dashboard));
My actionCreator goes like:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
const local_token = localStorage.getItem('_token');
const fullToken = 'Bearer '.concat(local_token);
axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
});
} else {
axios.get(url)
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
console.log(error);
dispatch(productsIsLoading(false));
dispatch(productsErrors(error.message));
});
}
};
};
Now, I want my getProductsAndPackagesActionCreator() to return a Promise or anything that would allow my something variable to get the actual data returned from the server. Right now, by the time I'm getting actual data, the line something=this.props.products has already been executed and I get back the initialValue that was set for products.
I know, whenever I'll receive the populated products, component will re-render, but that does not help my decision making.
I'm using redux-thunk, by the way.
What should I do now ? Sorry for such a long post.
Actually I wanted getProductsAndPackagesActionCreator() to return a promise, which was pretty straightforward, to be honest. I figured out that if you just return the axios.get() or axios.post(), it will return a promise. So, the modified code looked like below:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
return axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
............
............
})
.catch(error => {
});
} else {
return axios.get(url)
.then(response => {
...........
...........
})
.catch(error => {
console.log(error);
});
}
};
};
And then, I could do something like below in componentDidMount() or on any onClick event:
this.props.getProductsAndPackages().then(() => {
this.setState({
...this.state,
clicked_product: this.props.product_by_id
}, () => {
//do other stuffs
});
});
Feel free to let me know if there's any issue.
I think you are close to getting what you want. First of all, you should understand that redux actions and react actions like setState are asynchronous, so you have to apply your logic keeping this in mind. I'm going to explain what i think in some points:
You have called the action creator in the correct place componentDidMount, also you can call this action in any onClick if you want.
As soon as you dispatch the action you are changing your redux state setting loading true I suppose. So now you can access this property in your render function, so you can render a Loader until your api call finishes.
When your ajax function finishes, with an error or not, I suppose you are setting loading to false and updating your products data, so you can render now your loaded products in your dashboard.
Are you sure that you have to compare your empty products array with the received data? Maybe you can check in your render function if (!this.props.products.length) return null, when you load your page you will see a loader function and later your dashboard with the products.
If you really need to compare previous products with received products componentDidUpdate is your method. In this method, you can access your previous props and compare with actual props, be careful comparing arrays, remember [] === [] is false. Maybe you can compare the length, something like
componentDidUpdate(prevProps){
if(prevProps.products.length !=== this.props.products.lenth){
doSomething()
}
}
Just to say that componentDidUpdate is executed after render, so be careful with your code to no-execute extra renderings.
Hope it helps, if you dont understand anyting just tell me :)

How to set state of response from axios in react

How do I set the state of a get response in axios?
axios.get(response){
this.setState({events: response.data})
}
You have a syntax error here. You should try this instead
var self = this;
axios.get('/url')
.then(function (response) {
console.log(response);
self.setState({events: response.data})
})
.catch(function (error) {
console.log(error);
});
//the rest of the code
var a = 'i might be executed before the server responds'
There are a few things to note here:
axios.get is an asynchronous function which means that the rest of the code will be executed .And when the response of the server arrives, the function passed to then will be executed. The return value of axios.get('url') is called a promise object. You can read more about it here
this keyword has a different value depending of where it is called. this in this.setState should refer to the constructor object, and when you call this inside a function, it refers to the window object. That is why i assigned this to the variable self. You can read more about this here
Pro tip:
If you use ES6, you would want to use arrow functions (which don't have their own this) and use this.setState without assigning this to a variable. more about it here
axios.get('/url')
.then((response) => {
console.log(response);
this.setState({events: response.data})
})
.catch((error)=>{
console.log(error);
});
Here is a complete example https://codesandbox.io/s/rm4pyq9m0o containing best practices commonly used to fetch data including error handling, try again and loading. This provides a better User experience. You are encouraged to modify the code and play around to get more insights about it.
This isn't working because "this" is different inside of axios. "this" inside axios refers to the axios object, not your react component. You can resolve this with .bind
Also axios isnt being used properly.
it should look something like
axios.get("/yourURL").then(function(response) {
this.setState({ events: response.data });
}.bind(this));
Alternatively if using es6 you could sub out the function for an arrow function and get the same effect without bind
axios.get("/yourURL").then(response => {
this.setState({ events: response.data });
});
Simply try this node js
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
if you are using react js then you first import in component than use axios
like this:
import React from 'react';
import axios from 'axios';
export default class PersonList extends React.Component {
state = {
persons: []
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}
render() {
return (
<ul>
{ this.state.persons.map(person => <li>{person.name}</li>)}
</ul>
)
}
}
I have dealt with promises similar to that in the past when I was learning react. What I did was put the api call on the componentDidMount method and set the state to an initial value. I used a loader while the data was being fetched.
componentDidMount() {
const self = this;
axios.get(response){
self.setState({ events: response.data });
}
As of now, I would use something similar to what checkenrode said.
Do something like this:
var self= this; // self will now be referred to your component
axios.get("http://localhost:3001/get_user?id=" + id)
.then(function (response) {
if(response.data.rows != null)
user_detail = response.data.rows;
console.log(response);
self.setState({email: user_detail.name, name: user_detail.name})
})

Using the MobX #action decorator with async functions and .then

I'm using MobX 2.2.2 to try to mutate state inside an async action. I have MobX's useStrict set to true.
#action someAsyncFunction(args) {
fetch(`http://localhost:8080/some_url`, {
method: 'POST',
body: {
args
}
})
.then(res => res.json())
.then(json => this.someStateProperty = json)
.catch(error => {
throw new Error(error)
});
}
I get:
Error: Error: [mobx] Invariant failed: It is not allowed to create or change state outside an `action` when MobX is in strict mode. Wrap the current method in `action` if this state change is intended
Do I need to supply the #action decorator to the second .then statement? Any help would be appreciated.
Do I need to supply the #action decorator to the second .then statement? Any help would be appreciated.
This is pretty close to the actual solution.
.then(json => this.someStateProperty = json)
should be
.then(action(json => this.someStateProperty = json))
Keep in mind action can be called in many ways that aren't exclusive to #action. From the docs on action:
action(fn)
action(name, fn)
#action classMethod
#action(name) classMethod
#action boundClassMethod = (args) => { body }
#action(name) boundClassMethod = (args) => { body }
are all valid ways to mark a function as an action.
Here's a bin demonstrating the solution: http://jsbin.com/peyayiwowu/1/edit?js,output
mobx.useStrict(true);
const x = mobx.observable(1);
// Do async stuff
function asyncStuff() {
fetch('http://jsonplaceholder.typicode.com/posts')
.then((response) => response.json())
// .then((objects) => x.set(objects[0])) BREAKS
.then(mobx.action((objects) => x.set(objects[0])))
}
asyncStuff()
As for why your error actually happens I'm guessing that the top level #action doesn't recursively decorate any functions as actions inside the function it's decorating, meaning your anonymous function passed into your promise wasn't really an action.
To complement the above answer; indeed, action only works on the function you pass to it. The functions in the then are run on a separate stack and should therefor be recognizable as separate actions.
Note that you can also give the actions a name as well so that you easily recognize them in the devtools if you use those:
then(action("update objects after fetch", json => this.someStateProperty = json))
note that in async method you manualy have to start a new action/transaction after awaiting something:
#mobx.action async someAsyncFunction(args) {
this.loading = true;
var result = await fetch(`http://localhost:8080/some_url`, {
method: 'POST',
body: {
args
}
});
var json = await result.json();
#mobx.runInAction(()=> {
this.someStateProperty = json
this.loading = false;
});
}
my preference
I prefer to not use #mobx.action/runInAction directly but always place it on an private method. And let public methods call private methods that actually update the state:
public someAsyncFunction(args) {
this.startLoading();
return fetch(`http://localhost:8080/some_url`, {
method: 'POST',
body: {
args
}
})
.then(res => res.json())
.then(this.onFetchResult);
}
#mobx.action
private startLoading = () => {
this.loading = true;
}
#mobx.action
private onFetchResult = (json) => {
this.someStateProperty = json;
this.loading = false;
}

Categories

Resources