I have the following block of code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
avatar: '',
...some more data...
}
this.fetchUser = this.fetchUser.bind(this);
}
render() {
return (
<div>
<SearchBox onSubmit={this.fetchUser}/>
<Card data={this.state} />
<BaseMap center={this.state.address}/>
</div>
);
}
componentDidMount() {
function fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
};
function fetchApi(url) {
fetch(url)
.then((res) => res.json())
.... some data that is being fetched ....
});
};
let url = `https://api.github.com/users/${this.state.username}`;
}
}
export default App;
However, I get a TypeError: Cannot read property 'bind' of undefined for the following line: this.fetchUser = this.fetchUser.bind(this); in the constructor where I bind the function to this.
How can I make the function, which is inside componentDidMount method, visible and available for binding?
EDIT:
The reason I'm putting the functions inside componentDidMount was because of a suggestion from another user on StackOverflow. He suggested that:
#BirdMars that because you don't really fetch the data in the parent,
and the state doesn't really hold the address object. call the fetch in
componentDidMount of the parent and update the state there. this will
trigger a second render that will pass in the new state with the
address object (the first render will be with an empty state.address
of course until you finish the fetch and update the state)
There is some fundamental misunderstanding here, you can still call the functions inside componentDidMount, however you should not define them inside it.
Simply define the functions outside componentDidMount and this will solve your problems, here is a short example
componentDidMount() {
this.fetchUser(this.state.username);
}
function fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
};
function fetchApi(url) {
fetch(url)
.then((res) => res.json())
.... some data that is being fetched ....
});
};
Its a simple matter of scope:
function outer(){
function inner(){
}
}
inner(); // error does not exist
As birdmars suggested you should call this.fetchUser() inside component did mount. but declare the function outside!
class App extends Component {
render() {
return (
<div>
<SearchBox onSubmit={this.fetchUser}/>
<Card data={this.state} />
<BaseMap center={this.state.address}/>
</div>
);
}
fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
};
fetchApi(url) {
fetch(url)
.then((res) => res.json())
.... some data that is being fetched ....
});
};
componentDidMount() {
let url = username; //frome somewhere, most probably props then use props Changed function instead!
var user = his.fetchUser(url)
this.setState(() => {user: user});
}
}
export default App;
If you declare the functions inside componentDidMount they are only visible in that scope and get be accessed by the component itself. Declare them in your component.
What he was talking about in this post was to call the functions from componentDidMount, but not to declare them in there.
"Another guy from StackOverflow" suggest you to call functions in componentDidMount() method, but not to define them there.
So you should do this instead
class App extends Component {
constructor(props) {
...
this.fetchUser = this.fetchUser.bind(this);
this.fetchApi= this.fetchApi.bind(this);
}
...
fetchUser(username) {...}
fetchApi(url) {...}
componentDidMount() {
this.fetchUser(...);
...
}
}
Related
I am trying to display the data of each character when I click the Info Button.
I know it is because in the onButtonClickHandler function it can not see the state. I have also tried this.state.person but it gives me an error saying "can not read state". And if I try just state.person it will give me "undefined".
What is the best way to do that? Thank you
API Link: https://swapi.dev/people/
import React from "react";
export default class FetchActors extends React.Component {
state = {
loading: true,
person: null
};
async componentDidMount() {
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
this.setState({ person: data.results, loading: false });
}
render() {
if (this.state.loading) {
return <div>loading...</div>;
}
if (!this.state.person.length) {
return <div>didn't get a person</div>;
}
function onButtonClickHandler(state) {
console.log(state.person);
};
return (
<div>
<h1>Actors</h1>
{this.state.person.map(person =>(
<div>
<div>
{person.name}
<button onClick={onButtonClickHandler}>Info</button>
</div>
</div>
))}
<button onClick={onButtonClickHandler}>Enter</button>
</div>
);
}
}
Please correct me if I'm wrong
The most likely reason why you are seeing this is because of the way javascript internally works. The syntax:
function xyz() {
}
has an implicit this
Maybe try changing your code from:
function onButtonClickHandler(state) {
console.log(state.person);
};
to:
const onButtonClickHandler = () => {
console.log(this.state.person);
};
Further Reading: Here
You have defined your function onButtonClickHandler as a function that takes one argument, and logs the person property of that argument. The argument state in your function has nothing to do with the state of your component. As javascript sees it, they are two totally unrelated variables which just happen to have the same name.
function onButtonClickHandler(state) {
console.log(state.person);
};
When button calls onClick, it passes the event as the argument. So your onButtonClickHandler is logging the person property of the event, which obviously doesn't exist.
Since you are not using any information from the event, your function should take no arguments. As others have said, you should also move this function outside of the render() method so that it is not recreated on each render. The suggestion to use bind is not necessary if you use an arrow function, since these bind automatically.
export default class FetchActors extends React.Component {
/*...*/
onButtonClickHandler = () => {
console.log(this.state.person);
};
}
Inside render()
<button onClick={this.onButtonClickHandler}>Enter</button>
You could also define the function inline, as an arrow function which takes no arguments:
<button onClick={() => console.log(this.state.person)}>Enter</button>
If you are new to react, I recommend learning with function components rather than class components.
Edit:
Updating this answer regarding our comments. I was so caught up in explaining the errors from doing the wrong thing that I neglected to explain how to do the right thing!
I am trying to display the data of each character when I click the Info Button.
Once we call the API, we already have the info loaded for each character. We just need to know which one we want to display. You can add a property expanded to your state and use it to store the index (or id or name, whatever you want really) of the currently expanded item.
When we loop through to show the name and info button, we check if that character is the expanded one. If so, we show the character info.
Now the onClick handler of our button is responsible for setting state.expanded to the character that we clicked it from.
{this.state.person.map((person, i) =>(
<div>
<div>
{person.name}
<button onClick={() => this.setState({expanded: i})}>Info</button>
{this.state.expanded === i && (
<CharacterInfo
key={person.name}
person={person}
/>
)}
</div>
CodeSandbox Link
there are a few ways you can resolve your issue; I'll give you the more common approach.
You want to define your click handler as a class (instance) method, rather than declare it as a function inside the render method (you can define it as a function inside the render method, but that's probably not the best way to do it for a variety of reasons that are out of scope).
You will also have to bind it's 'this' value to the class (instance) because click handlers are triggered asynchronously.
Finally, add a button and trigger the fetch on click:
class Actors extends React.Component {
state = {
loading: false,
actors: undefined,
};
constructor(props) {
super(props);
this.fetchActors = this.fetchActors.bind(this);
}
async fetchActors() {
this.setState({ loading: true });
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
this.setState({ actors: data.results, loading: false });
}
render() {
console.log('Actors: ', this.state.actors);
return <button onClick={this.fetchActors}>fetch actors</button>;
}
}
Sometimes i takes react a min to load the updated state.
import React from "react";
export default class FetchActors extends React.Component {
state = {
loading: true,
person: null
};
async componentDidMount() {
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
if(!data.results) { // throw error }
this.setState({ person: data.results, loading: false }, () => {
console.log(this.state.person) // log out your data to verify
});
}
render() {
if (this.state.loading || !this.state.person) { // wait for person data
return <div>loading...</div>;
}else{
function onButtonClickHandler(state) { // just make a componentDidUpdate function
console.log(state.person);
};
return (
<div>
<h1>Actors</h1>
{this.state.person.map(person =>(
<div>
<div>
{person.name}
<button onClick={onButtonClickHandler}>Info</button>
</div>
</div>
))}
<button onClick={onButtonClickHandler}>Enter</button>
</div>
);
}
}}
I need to make a new api request to fetch data for a given dataId.
this value lives in the Context.
import { MyContext } from './Context'
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
dataId: this.context.state.dataId // tried setting state first but didn´t work.
}
this.details = this.details.bind(this)
}
details() {
fetch('https://api.mydomain.com/' + this.context.state.dataId)
.then(response => response.json())
.then(data => this.setState({ data: data }));
}
componentDidMount() {
this.details()
}
render() {
return(
<MyContext.Consumer>
{(context) => (
<div>data: {JSON.stringify(data)} dataId: {context.state.dataId}</div>
)}
</MyContext.Consumer>
)
}
}
MyComponent.contextType = MyContext;
export default MyComponent
from others components I can set new values like
this.context.setDataId(1)
and this will show up correctly but the problem is that is not making a new fetch to get new data for the dataId that changed in the Context.
not sure what´s the correct lifecycle method I can use to detect changes in the context and make a new call to this.details()
I didn´t add the Context code here because it works fine. but if you need to see it please let me know.
In react, you must use life cycle hooks to inspect data such as props or context, to know if the state needs to update for your component. The most common life cycle hook for this purpose is componentDidUpdate(). it gives you the ability to decide whether or not your component needs to update state/rerender based on changes in props that caused the component to update. the following should work for your use case:
import { MyContext } from './Context'
class MyComponent extends Component {
state = {
data:[],
dataId:null
}
details = () => {
// we only want to update if dataId actually changed.
if(this.context.state.dataId !== this.state.dataId){
this.setState({dataId:this.context.state.dataId});
fetch('https://api.mydomain.com/' + this.context.state.dataId)
.then(response => response.json())
.then(data => this.setState({ data: data }));
}
}
componentDidMount() {
this.details()
}
componentDidUpdate() {
this.details();
}
render() {
return(
<MyContext.Consumer>
{(context) => (
<div>data: {JSON.stringify(this.state.data)} dataId: {context.state.dataId}</div>
)}
</MyContext.Consumer>
)
}
}
MyComponent.contextType = MyContext;
export default MyComponent;
I can call data in console.log(this.state.eventUser); render return and its showing all of the data in eventUser. But when I try to call console.log(this.state.eventUser._id); its showing error of this Uncaught TypeError: Cannot read property '_id' of undefined. How can I solve this issue?
componentDidMount(){
Tracker.autorun(() => {
Meteor.subscribe('allUsers');
const userId = this.props.location.state.event.userID;
const eventUser = Meteor.users.findOne({_id: userId});
this.setState({ eventUser });
});
}
render(){
return(
<div>
{console.log(this.state.eventUser._id)}
</div>
);
}
Probably at some point this.state.eventUser in the render is undefined.
Try this
{this.state.eventUser && console.log(this.state.eventUser._id)
You can't use console.log inside JSX without enclosing in {}
Try below code
render(){
return(
<div>
{put javascript here}
</div>
);
}
Edit:
Your code is executing query on Database which is asynchronous
So you need to wait for it to complete.
Below is an implementation of async/await (es7+ features of javascript)
Try below code
componentDidMount(){
Tracker.autorun(async () => {
Meteor.subscribe('allUsers');
const userId = this.props.location.state.event.userID;
const eventUser = await Meteor.users.findOne({_id: userId});
this.setState({ eventUser });
});
}
It looks like by the time your render() function runs, the user hasn't yet be saved to the component state.
To fix this, add a constructor to your component in which you define the eventUser as an empty object.
class MyClass extends Component {
constructor(props) {
super(props);
this.state {
eventUser: {}
};
}
componentDidMount() {
Tracker.autorun(() => {
Meteor.subscribe('allUsers');
const userId = this.props.location.state.event.userID;
const eventUser = Meteor.users.findOne({_id: userId});
this.setState({ eventUser });
});
}
render(){
console.log(this.state.eventUser._id)
return(
<div>My Test Page</div>
);
}
}
Hope that helps!
componentDidMount will fire after the component has already been mounted (as the name states). At the point where it reaches the enclosed statement the state has not been set yet.
I'm not sure why you are using a console.log inside of your render return, but if you are looking to actually display the user ID, conditional rendering is your friend: {this.state.eventUser && this.state.eventUser._id}
For the first time when your component will render, if you have knowledge about react lifecycle hooks, after constructor the render method will be triggered then componentDidMount hook.
so for the first render the eventUser is undefined then after componentDidMount the state will be fulfilled.
So solution is:
First don't put console.log in JSX, it is better to put it before return.
Second first check if the object is defined then return your data
Code:
render(){
const { eventUser } = this.state;
// if eventUser is defined then log it
{eventUser && console.log(eventUser._id)}
// or you can add very simple load state
if(!eventUser) return <h1>Loading....</h1>
return(
<div>
{probably info about user }
</div>
);
}
I am new to ReactJS and am unsuccessfully attempting to manage a state change. The initial state renders as expected, the state successfully changes, however the elements do not render afterwards. There are no errors in the DOM console to go off of. I've made sure to set the initial state in the constructor of the component class, and I've also tried binding the method I'm using in the constructor since I've read auto-binding is not a part of ES6. The relevant component code is as follows:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
myIDs: Array(6).fill('0')
};
this.getMyIDs = this.getMyIDs.bind(this);
};
componentDidMount() {
var ids = this.getMyIDs();
ids.then((result)=> {
this.setState({ myIDs: result }, () => {
console.log(this.state.myIDs)
});
})
};
componentWillUnmount() {
this.setState({
myIDs: Array(6).fill('0')
});
};
getMyIDs() {
return fetch('/api/endpoint').then((response) =>{
return response.json();
}).then((myIDs) => {
return myIDs.result
})
};
render() {
return (
<Tweet tweetId={this.state.myIDs[0]} />
<Tweet tweetId={this.state.myIDs[1]} />
);
}
}
export default MyComponent
UPDATE: The 'element' being updated is the 'Tweet' component from react-twitter-widgets. Its source is here:
https://github.com/andrewsuzuki/react-twitter-widgets/blob/master/src/components/Tweet.js'
export default class Tweet extends React.Component {
static propTypes = {
tweetId: PropTypes.string.isRequired,
options: PropTypes.object,
onLoad: PropTypes.func,
};
static defaultProps = {
options: {},
onLoad: () => {},
};
shouldComponentUpdate(nextProps) {
const changed = (name) => !isEqual(this.props[name], nextProps[name])
return changed('tweetId') || changed('options')
}
ready = (tw, element, done) => {
const { tweetId, options, onLoad } = this.props
// Options must be cloned since Twitter Widgets modifies it directly
tw.widgets.createTweet(tweetId, element, cloneDeep(options))
.then(() => {
// Widget is loaded
done()
onLoad()
})
}
render() {
return React.createElement(AbstractWidget, { ready: this.ready })
}
}
As in React docs:
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. Generally, we
recommend using the constructor() instead.
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
you should not use ajax calls in componentWillMount
call ajax inside: componentDidMount
another thing: why do you use
componentWillUnmount
the object will be removed no reason to have that call there.
The only issue that is present in your current code is that you are returning multiple Element component instances without wrapping them in an array of a React.Fragment or a wrapper div. With the latest version of react, you must write
render() {
return (
<React.Fragment>
<Element Id={this.state.myIDs[0]} />
<Element Id={this.state.myIDs[1]} />
</React.Fragment>
);
}
}
Also as a practice you must have your Async calls in componentDidMount instead of componentWillMount as the React docs also suggest. You might want to read this answer on where write async calls in React for more details
Another thing that you must remember while using prop Id in your Element component is that componentWillMount and componentDidMount lifecycle functions are only called on the initial Render and not after that, so if you are using this.props.Id in one of these function in Element component then you will not be able to see the update since the result of async request will only come later, check this answer on how to tacke this situation
I am trying to use axios to get data from the api (https://reqres.in/) and display in my react app. Before this I fetched the data from the API using fetch method in javascript. Now I have tried coding this from various resources. How should I do it. Is it the correct method?
My app.js file-
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.successShow = this.successShow.bind(this);
this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(function (response) {
this.successShow(response);
})
.catch(function (error) {
this.errorShow(error);
});
}
successShow(response) {
this.member = <pre>{JSON.stringify(response.data, null, '\t')}</pre> ;
}
errorShow(error) {
this.member = <pre>{JSON.stringify(error.response.data, null, '\t')}</pre>;
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<div>{this.member}</div>
</div>
);
}
}
export default App;
It also gives the error - Unhandled Rejection (TypeError): Cannot read property 'errorShow' of undefined.
Changes:
1. You need to bind this with then and catch callback methods, use arrow functions.
2. You didn't define the initial state and using this.state.person.data it will throw error.
3. Storing the UI in state or global variable is not a good idea, ui part should be inside render method only.
Write it like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
person: {}
}
//this.successShow = this.successShow.bind(this);
//this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.successShow(error);
});
}
successShow(response) {
this.setState({
person: response.data
});
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<pre>{JSON.stringify(this.state.person.data)}</pre>
<div>{this.member}</div>
</div>
);
}
}
When you call this.errorShow() inside of the function, this is not your component object, but context of function. You should use arrow functions instead, arrow functions do not create its own this so you can access your component this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch(error) => {
this.errorShow(error);
});
}
More info about arrow functions
Try this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.errorShow(error);
});
}
Use arrow functions to remain the right scope of this
The problem is that the this in your then and catch callbacks doesn't refer to your class, but to the default (global) scope. You need to bind the right this. You actually already have the appropriate functions set up with this binding, so you can just use them directly:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(this.successShow)
.catch(this.errorShow);
}
In general, you can also use => function syntax, which inherits the 'this' from the scope the function is declared in, rather than using the global scope. E.g.
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(success => this.successShow(success))
.catch(error => this.errorShow(error));
}
(note the => functions are completely unnecessary here of course).
You have an additional problem, which is the you need to store member in component state (this.state.member), not just as a field, and use the setState function to update it. Otherwise your component won't re-render when you update member.