Working on this project and trying to call firebase real-time database information then setting a state after calling it so I can use it in the render. This is my code currently but its saying setState is unknown, I've tried to read up on other solutions to this problem but don't understand it, any help would be appreciated. Thanks.
componentDidMount(){
this._getLocationAsync();
firebase.database().ref('pets/').once('value', function (snapshot) {
this.setState({ testdid: snapshot.val().name })
});
}
The short answer is because this in your firebase callback refers to that function itself, rather than the component. Using an arrow function should correctly bind this to the component should fix your error:
firebase.database().ref('pets/').once('value', (snapshot) => {
this.setState({ testdid: snapshot.val().name })
});
Read up on scope in JS, particularly in regards to the this keyword. Definitely important to know, cos it has some weird behaviour sometimes.
Your callback is made in different context, you need to do :
componentDidMount(){
this._getLocationAsync();
firebase.database().ref('pets/').once('value', function (snapshot) {
this.setState({ testdid: snapshot.val().name })
}.bind(this)); // bind to current context
}
or with ES6, which i prefer
componentDidMount(){
this._getLocationAsync();
firebase.database().ref('pets/').once('value', snapshot => { // Using Fat Arrow
this.setState({ testdid: snapshot.val().name })
});
}
Related
I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )
I am trying to get video from a specific channel in youtube using youtube data api using reactjs.But my api query doesnot return anything.Here is what i've tried for getting the video.What am i doing wrong here.
componentDidMount() {
console.log('componentDidMount colling ...');
fetch('https://www.googleapis.com/youtube/v3/channels?key="MYAPIKEY"&channelId="MYCHANNELID"&part=snippet,id&order=date&maxResults=2')
.then(results => {
this.setState({videos: results.json()});
this.setState({playingVideoId: this.state.videos[this.index]});
console.log("videos",this.state.videos)
console.log("videos",this.state.playingVideoId)
})
}
First try to use postman to see if URL is returning correct data.
Second: setState is asynchronous which means, if you console.log right after setting state, you won't get correct results. Also, your second call to setState is using data from first call therefore you won't get right results. Just call setState once and use callback to log what you got.
componentDidMount() {
console.log('componentDidMount colling ...');
fetch('https://www.googleapis.com/youtube/v3/channels?key="MYAPIKEY"&channelId="MYCHANNELID"&part=snippet,id&order=date&maxResults=2')
.then(results => {
const videosObj = results.json();
this.setState({
videos: videosObj,
playingVideoId: videosObj[this.index]
}, (updatedState) => {
console.log("videos", updatedState.videos);
console.log("videos", updatedState.playingVideoId);
});
})
}
I'm using gatsby for server side rendering.
Here's my code:
class BookSearch extends Component {
state = {
search: '',
books: '',
};
componentDidMount() {
this.loadData()
}
loadData () {
axios.get('/books/list')
.then(response => {
this.setState({books: response.data.books});
console.dir(response.data.books);
})
.catch(error => {
this.setState({error: true});
});
}
Unfortunately, this.setState does not work in gatsby. componentDidMount is not being called when I load the page. What should I do?
I think the issue is of binding this to loadData method.
You can bind this in 2 ways.
Bind this in the constructor,
constructor(props){
super(props)
this.state = {
search: '',
books: '',
}
this.loadData = this.loadData.bind(this) //Bind this here
}
Or you can simply use arrow function,
loadData = () => { //Arrow function auto binds `this`
axios.get('/books/list')
.then(response => {
this.setState({
books: response.data.books
});
console.dir(response.data.books);
})
.catch(error => {
this.setState({error: true});
});
}
I think you should have got an error? It's because you have not initialized error state. You must initialize state before you can use them:
state = {
search: '',
books: '',
error: false
};
I hope this may fix the issue. Otherwise, I couldn't see any issue in your code.
You mentioned you're using SSR?
Try using componentWillMount in this case, since componentDidMount is not called in SSR.
In case you're using react version > 16.3:
When supporting server rendering, it’s currently necessary to provide the data synchronously – componentWillMount was often used for this purpose but the constructor can be used as a replacement. The upcoming suspense APIs will make async data fetching cleanly possible for both client and server rendering.
Reference: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data
In your case, I think it would make more sense to use the getInitialProps static method. (https://nextjs.org/learn/basics/fetching-data-for-pages/fetching-batman-shows)
If you're not very familiar with SSR, Next.js has great tutorials:
https://nextjs.org/learn/basics/getting-started
This may help you out!
Is using componentDidMount() as an async function good practice in React Native or should I avoid it?
I need to get some info from AsyncStorage when the component mounts, but the only way I know to make that possible is to make the componentDidMount() function async.
async componentDidMount() {
let auth = await this.getAuth();
if (auth)
this.checkAuth(auth);
}
Is there any problem with that and are there any other solutions to this problem?
Let's start by pointing out the differences and determining how it could cause troubles.
Here is the code of async and "sync" componentDidMount() life-cycle method:
// This is typescript code
componentDidMount(): void { /* do something */ }
async componentDidMount(): Promise<void> {
/* do something */
/* You can use "await" here */
}
By looking at the code, I can point out the following differences:
The async keywords: In typescript, this is merely a code marker. It does 2 things:
Force the return type to be Promise<void> instead of void. If you explicitly specify the return type to be non-promise (ex: void), typescript will spit an error at you.
Allow you to use await keywords inside the method.
The return type is changed from void to Promise<void>
It means you can now do this:
async someMethod(): Promise<void> { await componentDidMount(); }
You can now use await keyword inside the method and temporarily pause its execution. Like this:
async componentDidMount(): Promise<void> {
const users = await axios.get<string>("http://localhost:9001/users");
const questions = await axios.get<string>("http://localhost:9001/questions");
// Sleep for 10 seconds
await new Promise(resolve => { setTimeout(resolve, 10000); });
// This line of code will be executed after 10+ seconds
this.setState({users, questions});
return Promise.resolve();
}
Now, how could they cause troubles?
The async keyword is absolutely harmless.
I cannot imagine any situation in which you need to make a call to the componentDidMount() method so the return type Promise<void> is harmless too.
Calling to a method having return type of Promise<void> without await keyword will make no difference from calling one having return type of void.
Since there is no life-cycle methods after componentDidMount() delaying its execution seems pretty safe. But there is a gotcha.
Let's say, the above this.setState({users, questions}); would be executed after 10 seconds. In the middle of the delaying time, another ...
this.setState({users: newerUsers, questions: newerQuestions});
... were successfully executed and the DOM were updated. The result were visible to users. The clock continued ticking and 10 seconds elapsed. The delayed this.setState(...) would then execute and the DOM would be updated again, that time with old users and old questions. The result would also be visible to users.
=> It is pretty safe (I'm not sure about 100%) to use async with componentDidMount() method. I'm a big fan of it and so far I haven't encountered any issues which give me too much headache.
Update April 2020:
The issue seems to be fixed in latest React 16.13.1, see this sandbox example. Thanks to #abernier for pointing this out.
I have made some research, and I have found one important difference:
React does not process errors from async lifecycle methods.
So, if you write something like this:
componentDidMount()
{
throw new Error('I crashed!');
}
then your error will be caught by the error boundary, and you can process it and display a graceful message.
If we change the code like this:
async componentDidMount()
{
throw new Error('I crashed!');
}
which is equivalent to this:
componentDidMount()
{
return Promise.reject(new Error('I crashed!'));
}
then your error will be silently swallowed. Shame on you, React...
So, how do we process errors than? The only way seems to be explicit catch like this:
async componentDidMount()
{
try
{
await myAsyncFunction();
}
catch(error)
{
//...
}
}
or like this:
componentDidMount()
{
myAsyncFunction()
.catch(()=>
{
//...
});
}
If we still want our error to reach the error boundary, I can think about the following trick:
Catch the error, make the error handler change the component state
If the state indicates an error, throw it from the render method
Example:
class BuggyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
buggyAsyncfunction() { return Promise.reject(new Error('I crashed async!'));}
async componentDidMount() {
try
{
await this.buggyAsyncfunction();
}
catch(error)
{
this.setState({error: error});
}
}
render() {
if(this.state.error)
throw this.state.error;
return <h1>I am OK</h1>;
}
}
Your code is fine and very readable to me. See this Dale Jefferson's article where he shows an async componentDidMount example and looks really good as well.
But some people would say that a person reading the code may assume that React does something with the returned promise.
So the interpretation of this code and if it is a good practice or not is very personal.
If you want another solution, you could use promises. For example:
componentDidMount() {
fetch(this.getAuth())
.then(auth => {
if (auth) this.checkAuth(auth)
})
}
When you use componentDidMount without async keyword, the doc say this:
You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen.
If you use async componentDidMount you will loose this ability: another render will happen AFTER the browser update the screen. But imo, if you are thinking about using async, such as fetching data, you can not avoid the browser will update the screen twice. In another world, it is not possible to PAUSE componentDidMount before browser update the screen
I think it's fine as long as you know what you're doing. But it can be confusing because async componentDidMount() can still be running after componentWillUnmount has run and the component has unmounted.
You may also want to start both synchronous and asynchronous tasks inside componentDidMount. If componentDidMount was async, you would have to put all the synchronous code before the first await. It might not be obvious to someone that the code before the first await runs synchronously. In this case, I would probably keep componentDidMount synchronous but have it call sync and async methods.
Whether you choose async componentDidMount() vs sync componentDidMount() calling async methods, you have to make sure you clean up any listeners or async methods that may still be running when the component unmounts.
Update:
(My build: React 16, Webpack 4, Babel 7):
When using Babel 7 you'll discover:
Using this pattern...
async componentDidMount() {
try {
const res = await fetch(config.discover.url);
const data = await res.json();
console.log(data);
} catch(e) {
console.error(e);
}
}
you will run into the following error...
Uncaught ReferenceError: regeneratorRuntime is not defined
In this case you will need to install babel-plugin-transform-runtime
https://babeljs.io/docs/en/babel-plugin-transform-runtime.html
If for some reason you do not wish to install the above package (babel-plugin-transform-runtime) then you will want to stick to the Promise pattern...
componentDidMount() {
fetch(config.discover.url)
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(err => console.error(err));
}
I like to use something like this
componentDidMount(){
const result = makeResquest()
}
async makeRequest(){
const res = await fetch(url);
const data = await res.json();
return data
}
Actually, async loading in ComponentDidMount is a recommended design pattern as React moves away from legacy lifecycle methods (componentWillMount, componentWillReceiveProps, componentWillUpdate) and on to Async Rendering.
This blog post is very helpful in explaining why this is safe and providing examples for async loading in ComponentDidMount:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
To Tag on to #C-F's answer, I added a typescript decorateor
(AsyncMethodErrorHandler) to handle errors in async componentDidMount() and other async methods that fail to bubble up errors to
the application state.
I found this easier than wrapping dozens of async methods in a try/catch block in an app
whose maintainince I inherited.
class BuggyComponent extends React.Component<{error_message?:string}> {
#AsyncMethodErrorHandler("error_message")
async componentDidMount() {
await things_that_might_fail();
}
render(){
if(this.state.error_message){
return <p>Something went wrong: {this.state.error_message}</p>
}
}
}
function AsyncMethodErrorHandler(
/* Key in the this.state to store error messages*/
key: string,
/* function for transforming the error into the value stored in this.state[key] */
error_handler: string | { (e: Error): string } = (e: Error) => e.message
) {
return function (
cls: React.Component,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const f: { (...args: any[]): Promise<any> } = descriptor.value;
return {
...descriptor,
value: function (...args: any[]) {
return f.apply(this, args).catch((e: Error) => {
console.log(`an error occured in the ${propertyKey} Method:`, e);
(this as any as React.Component).setState({
[key]:
typeof error_handler === "string"
? error_handler
: error_handler(e),
});
});
},
};
};
}
Note that as of this writing, this solution does not work for async function properites because:
property decorator[s] can only be used to observe that a property of a
specific name has been declared for a class
From my react application I made an Ajax request to the spring Mvc controller, got a Jason response from the requested spring controller.
Now am trying to set this response inside my componentDidMont () to a state defined inside the constructor using this.setState method using react js.
When I tried to set the state using this.setState method I get Type Error:
Cannot read property 'set State' of undefined error
Please do help me fix this error.
Course.js
import React from "react";
import Article from "../components/Article";
import axios from 'axios';
export default class Courses extends React.Component {
constructor() {
super();
this.state={items:[]};
}
componentDidMount() {
console.log("hello");
axios.get('http://localhost:8080/ulearn/rest/course/getAll').then(function (response) {
this.setState({items: response});
console.log(response);
}).catch(function (error) {
console.log(error);
});
}
Render method
render() {
return (
<div>
<div>Response - {this.state.items}</div>;
</div>
);
} }
Below is the error message:
In Javascript, defining an anonymous function does not automatically inherit the this context from the upper scope. So when you define an anonymous function callback for your ajax request, this becomes undefined inside it. Since you are already using ES6 features (like import and class) you can also use arrow functions with => to overcome the issue. They behave like normal functions, but do not create a new this context. So you can simply do this in your componentDidMount:
axios.get('http://localhost:8080/ulearn/rest/course/getAll')
.then((response) => {
this.setState({items: response});
console.log(response);
}).catch((error) => {
console.log(error);
});
As per the error in your comment, as the response above mine beat me to answering your question :) -
You are trying to print out the entire object, try printing out each item individually or adding .toString() behind this.state.items in your render method. The same thing happens when you try to print new Date() for example, it won't let you print out the entire object, but it will let you print a string representation of the object, so new Date().toString() would work for example.