React script
class TransactionsList extends Component {
constructor(props) {
super(props);
this.state = {
activeAccountId: "",
accessToken: "",
TransactionsData: "",
};
}
replaceRoute(route, passProps) {
this.props.replaceRoute(route, passProps);
}
async _getToken() {
try {
let accessToken = await AsyncStorage.getItem('AUTH_TOKEN');
if(!accessToken) {
this.replaceRoute('login');
} else {
this.setState({accessToken: accessToken})
}
} catch(error) {
Alert.alert('Print Errorr', error.message)
this.replaceRoute('login');
}
}
componentWillMount(){
this._getToken()
let token = 'Token '+this.state.accessToken
this.load_data(token)
}
render() {
return (
<Container>
// other code
</Container>
)
}
}
Got error in setState in getToken below is catch(error) block output
Print Error null is not an object(evaluating
prevComponentInstance._currentElement)
But same above code works in other screens.
It is not advisable to make api calls in componentWillMount because it is possible that the component will not have been mounted when the api call has finished and you call setState.
Instead, you should make api calls in componentDidMount. According to the documentation:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
And, you also need to bind _getToken as #Jazib mentioned.
You need to bind _getToken method using something like this:
this._getToken().bind(this)
Or you can do this in the constructor for better code (I prefer this one):
constructor(props) {
super(props);
this.state = {
activeAccountId: "",
accessToken: "",
TransactionsData: "",
};
this._getToken() = this._getToken().bind(this)
}
Hope this helps
I know I am replying a bit late but a better way is to use an arrow function instead of using bind on a named function. So you could write your _getToken method like this:
const _getToken = async () => {
// your logic here
}
The arrow function here implicitly assigns the current instance of the component to this keyword whereas in the named function you have to give the this keyword the context by using bind method as mentioned by others.
Also, componentWillMount is now deprecated and its better if you call your method in componentDidMount
Related
For a React app that I inherited from another developer, one of the pages includes:
import { getLogUser } from "../../appRedux/actions/authAction";
constructor(props) {
super(props);
this.state = {
user: null,
};
}
UNSAFE_componentWillMount() {
let user = getLogUser();
this.setState({ user });
// user state is used inside the render part
}
componentDidMount = () => {
let { username } = getLogUser();
// ... username is used inside some logic within the componentDidMount method.
I would like to get rid of the UNSAFE_componentWillMount method.
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
If that is indeed the correct way to do it, shouldn't I then also
replace let { username } = getLogUser(); inside
componentDidMount with let { username } = this.state.user?
To start, let me explain what is UNSAFE_componentWillMount first
By defination
UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering.
So it means UNSAFE_componentWillMount() will be called before render() (the component has not been on UI yet). This is totally opposite of componentDidMount() which is called after render()
To go deeper into why React's team wanted to make it UNSAFE as for a deprecated function, you can check this RFC.
Following up on your questions
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
The benefit to having your function calls in the constructor is similar to UNSAFE_componentWillMount which makes sure your data available before rendering trigger.
So I'd say yes for your case, you can do it as long as it's not an asynchronous function (like async/await)
constructor(props) {
super(props);
this.state = {
user: await getLogUser(), //YOU CANNOT DO THIS WAY
};
}
This is the correct way
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
So what if getLogUser() is asynchronous? componentDidMount comes in handy. It will be triggered after first rendering but you can wait for your data as much as you want and beyond that, it won't block your UI's interactions (or you can show a loading UI instead)
componentDidMount = async () => {
const user = await getLogUser()
setState({ user })
}
render() {
//show loading if `user` data is not populated yet
const { user } = this.state
if(!user) {
return <div>Loading</div>
}
}
If that is indeed the correct way to do it, shouldn't I then also replace let { username } = getLogUser(); inside componentDidMount with let { username } = this.state.user?
Yes, indeed. You can do it if you already populate user state in constructor, but you need to ensure your function will be executed in a small amount of time. If your function call takes too long, that will cause UI problems due to the blocked rendering.
//trigger before first rendering
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
//trigger after first rendering
componentDidMount = () => {
const { username } = this.state.user;
}
How do I gain acceess to promises so that I can use for example the bitcoin price on my website?
axios.get('https://api.coinmarketcap.com/v1/ticker/?convert=EUR&limit=10')
.then(function(response){
console.log(response.data[0].price_usd);
});
Here is a codepen with a sample of the code.
https://codepen.io/albin996/pen/LzLZYX?editors=1112
We should start by noting that external requests should be carefully handled in React so the actual reactivity works well keeping its performance. That's why we're going to create a class to holds this logic in a organized way.
const URL = 'https://api.coinmarketcap.com/v1/ticker/convert=EUR&limit=10';
// Our component now is a full class
class BitcoinDisplay extends React.Component {
constructor(props) {
super(props);
// Start with no response available
this.state = {response: false};
}
// Waits the component to be rendered before calling API
componentDidMount() {
axios.get(URL).then(response => {
// Updates the state with the response
this.setState({ response })
});
}
// Renders the component with the available data
render() {
if(!this.state.response) {
// Show a loading state because data may not be available yet
return 'Loading data...';
} else {
return (<h1>{response.data[0].price_usd}</h1>);
}
}
}
Then, you render it inside the DOM.
ReactDOM.render(BitcoinDisplay, document.getElementById('app'));
I have App.js file and it is root of my application (both ios and android reference to it).
I have a value that I keep in AsyncStorage that I need before app.js render method is called.
Problem is that as it is async it late and I can't get that value.
class App extends React.Component {
constructor(props) {
super(props);
this.init()
}
async init() {
try {
const value = await AsyncStorage.getItem('#myPoorValue:key');
if (value !== null){
...
}
} catch (error) {}
}
}
...
render (...
I hope that I explained good what is my issue here.
I know that there is no way to get it synchronous (I would like that) but don't know what to do in this situation.
To explain it a bit better I use I18n and I manually set I18n.locale to some value and other components get default value before I set it manually.
Just to note I also use redux and I pass selected value to it.
try the following:
...
constructor(props) {
super(props)
this state = {
isLoading: true
}
}
async componentDidMount() {
await this.init()
// you might want to do the I18N setup here
this.setState({
isLoading: false
})
}
async init() {
const value = await AsyncStorage.getItem('#myPoorValue:key')
...
}
...
the thing is that init() returns a promise and you need to wait until it gets resolved. That's when await comes to rescue.
you'll also need to set up some loader that will be there on first render, and toggle the state to replace it with actual markup after the AsyncStorage value has been fetched. I've put it in the code, but you might want to trigger a redux action instead, depending on your setup.
I want to populate two tokens properties via AJAX whenever the state is created. It seems that Redux doesn't have much documentation on this. I can't use componentWillMount to do this because the way I have my containers set up it just won't work.
const Auth = Record({
token: '',
yelpToken: '',
});
Is there someway run a function that will happen before createStore is invoked?
You can replace your index with this:
class EntryPoint extends Components {
constructor(){
this.state = {
//can be replaced with store state..
finishedFetching: false
}
}
componentDidMount() {
//you can chain dispatches with redux thunk
dispatch(fetchAsyncData())
.then(() => this.setState({finishedFetching: true}))
}
render() {
return this.state.finishedFetching ? <App/> : null;
}
}
I'm accessing a promise that gets returned from my mock API. The React component looks like what you see below
import React from 'react';
import StudentListStatistics from './StudentListStatistics';
import StudentStatisticsApi from '../../api/mockStudentApi';
class AboutPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
studentsStatistics: []
};
}
componentDidMount() {
StudentStatisticsApi.getAllStudentsStatistics().then(
studentsStatistics => {
this.setState({
studentsStatistics: studentsStatistics
});
debugger;
}
);
console.log(this.state.studentsStatistics);
}
render() {
return (
<div>
<h2>Student Body Statistics</h2>
<StudentListStatistics studentsStatistics={this.state.studentsStatistics}/>
</div>
);
}
the mock API looks like this
class StudentApi {
static getAllStudentsStatistics() {
return new Promise((resolve, reject)=> {
setTimeout(()=> {
resolve(Object.assign([], studentsStatistics));
}, 1000);
});
}
I'm not sure why the this.state.studentsStatistics is always an empty array. If I step through the code then studentsStatistics array is being returned correctly from my mock API within the then callback.
Can someone point out what I might be missing.
The issue is two-fold:
getAllStudentsStatistics() is asynchronous, which means that it will eventually yield a result, but not immediately;
setState() is also "asynchronous", in that it won't change this.state immediately after it got called.
To work around that, and log the mocked data, you need to first wait for the promise to resolve, and then to also wait for setState to acknowledge that the state has changed (by passing it a callback function):
componentDidMount() {
let promise = StudentStatisticsApi.getAllStudentsStatistics();
promise.then(studentsStatistics => {
this.setState({
studentsStatistics: studentsStatistics
}, () => {
console.log(this.state.studentsStatistics);
}
});
}
I think this also means that your StudentListStatistics component will initially be rendered with an empty array as input. Only once the promise has been resolved will it receive the mocked data.