Child component's state does not change while rendering - reactjs

I am a novice to ReactJs so please bear with me. I am trying to build a project from this course called Git-Hub profile viewer. Here is my code for Parent component:
import Profile from './github/Profile.jsx';
class App extends Component {
constructor(props){
super(props);
this.state = {
username: 'xxxx',
userData: [],
userRepos: [],
perPage: 5
}
}
// get user data from github
getUserData(){
$.ajax({
url: 'https://api.github.com/users/' +this.state.username+'?client_id='+this.props.clientId+'&client_secret='+this.props.clientSecret,
dataType:'json',
cache:false,
success: function(data){
this.setState({userData:data});
console.log(data);
}.bind(this),
error: function(xhr, status, err){
this.setState({userData: null});
alert(err);
}.bind(this)
});
}
componentDidMount(){
this.getUserData();
}
render(){
return (
<div>
<Profile userData = {this.state.userData} />
</div>
)
}
}
App.propTypes = {
clientId: React.PropTypes.string,
clientSecret: React.PropTypes.string
};
App.defaultProps = {
clientId: 'some_genuine_client_Id',
clientSecret:'some_really_client_secret'
}
export default App;
and here is my child's component:
class Profile extends Component {
render() {
return (
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">{this.props.userData.name}</h3>
</div>
<div className="panel-body">
</div>
</div>
)
}
}
export default Profile;
Problem is that the props in child component does not update its state while rendering the page although the data retrieval is successful from Git-Hub as shown in console log. What am I doing wrong, can somebody please help?

So firstly, you should be ensuring that you are accessing userData in the correct way, as #mersocarlin mentioned. This is quite likely to be the cause of your problem.
Theoretically, your way of doing this should work. However I've written a working jsfiddle for you as an example. I couldn't make the Ajax call so have simulated it with a setTimeout event over 5 seconds.
The other way of doing this is to pass down the getUserData function to the child component (Profile) and let it handle the call itself. Here is the jsfiddle for that, and the code below:
class Profile extends React.Component {
constructor(props) {
super(props);
this.state = {
userData: {}
};
}
componentDidMount() {
const userData = this.props.getUserData();
console.log(userData);
this.setState({ userData: userData });
}
render() {
return (
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">Hello, {this.state.userData.name || 'World'}</h3>
</div>
<div className="panel-body">
</div>
</div>
)
}
}
class App extends React.Component {
getUserData(){
return {
'name': 'Tom'
}
}
render(){
return (
<Profile getUserData={this.getUserData} />
)
}
};

First of all, you should call getUserData() in componentWillMount rather than componentDidMount to get the data before the component has rendered. And then in the child component use a life cycle method componentWillReceiveProps to check if the child component is getting the right props and updating the state accordingly. It'll be something like this:
componentWillReceiveProps(nextProps) {
if(this.props.userData !== nextProps.userData) {
console.log('condition met');
this.setState({
userData: nextProps.userData
});
}
}
Try this and let me know, I'll help you further.

Related

React error .map() is not a function after updating API data

I am currently working on a project where my react app connects to my own API. I've run into a problem that I can't seem to overcome. When I try to update my data I get an error saying map is not a function.
this is my vehicleOverview.js script this is supposed to show a list of all my available data which works fine until a I update a data element.
export class VehicleOverview extends React.Component{
constructor(props){
super(props);
this.state= {
vehicles: [],
}
getAll().then((response) => {
console.log(response.message);
this.setState({
vehicles: response.message,
});
}).catch((error) => {
console.error(error);
});
}
render(){
return(
<div >
<h2>Vehicle List</h2>
<ul >
{
this.state.vehicles.map((vehicle, i) => {
return(
<Vehicle key={`vehicle-${i}`} {...vehicle} />);
})
}
</ul>
</div>
);
}
}
export default VehicleOverview;
I am not sure what is going wrong because I am pretty sure vehicles is an array. Tell me if I need to provide more code to solve this problem
I noticed two things here:
Using this.setState in constructor is not recommended, because component is yet to be rendered.
Always do api calls from componentDidMount - For the reasons mentioned in this article
Also, just in case, add a condition before looping through vehicles.
import React from 'react';
export class VehicleOverview extends React.Component {
constructor(props) {
super(props);
this.state = {
vehicles: []
};
}
componentDidMount() {
getAll().then((response) => {
console.log(response.message);
this.setState({
vehicles: response.message
});
}).catch((error) => {
console.error(error);
});
}
render() {
return (
<div >
<h2>Vehicle List</h2>
<ul>
{this.state.vehicles && this.state.vehicles.map((vehicle, i) => (<Vehicle key={`vehicle-${i}`} {...vehicle} />))}
</ul>
</div>
);
}
}
export default VehicleOverview;
.map() function will work only for Arrays data.
check what typeof response you are getting.

React Redux: Delay between component inner content while updating

I have a component that have 2 components inside of it:
MyComp {
render (
html of My Comp..
<Loading show={this.props.isLoading}/>
<ErrorMessage show={this.props.hasError}/>
)
}
When it is receiving data, it shows the Loading.
When the loading is complete, it receive something like:
{
isLoading: false,
hasError: true
}
But in screen, the loading close like 2s before the hasError displays.
Both components are built in the same strategie:
class Loading extends Component {
constructor(props) {
super(props);
this.state = {isLoading : props.show};
}
componentWillReceiveProps(nextProps) {
this.setState({ isLoading: nextProps.show });
}
render() {
if (this.state.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
export default Loading;
Not exactly an answer for this issue, as i can't be sure where the delay can come from.
But according to your code i would suggest to not use a local state and try to sync it with external props.
This can lead to bugs (maybe related to your issue?) as componentWillReceiveProps can get invoked even when no new props received, beside it is in deprecation process since react V16.2.
Instead i would just read directly from this.props:
class Loading extends Component {
render() {
if (this.props.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
Again, not sure it is directly related to your issue but it is a better practice.

Clicking link not rendering dom in reactjs

First time data loads properly but when i click filter button like latest or top ajax is passing but view not getting updated. I am not sure what is wrong in my code. I am new to react js.
Here is my example code :-
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import css from './css/bootstrap.css';
//import Search from './Search';
class FetchDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [],
loading: true,
error: null
};
}
componentDidMount() {
// Remove the 'www.' to cause a CORS error (and see the error state)
axios.get(`https://newsapi.org/v1/articles?source=techcrunch&apiKey=789ea3cd651a49e5ba9fc2061d68138f`)
.then(res => {
//console.log(res.data);
// Transform the raw data by extracting the nested posts
const posts = res.data.articles;
//console.log(posts);
// Update state to trigger a re-render.
// Clear any errors, and turn off the loading indiciator.
this.setState({
posts,
loading: false,
error: null
});
//console.log(this.setState);
})
.catch(err => {
// Something went wrong. Save the error in state and re-render.
this.setState({
loading: false,
error: err
});
});
}
renderLoading() {
return <div>Loading...</div>;
}
renderError() {
return (
<div>
Uh oh: {this.state.error.message}
</div>
);
}
renderPosts() {
if(this.state.error) {
return this.renderError();
}
return (
<div className="row">
<First1/>
{this.state.posts.map(post =>
<div className="col-md-3">
<img src={post.urlToImage} className="img-responsive" />
<h2 key={post.id}>{post.title}</h2>
<p className="lead">
by {post.author}
</p>
<p><span className="glyphicon glyphicon-time"></span> Posted on {post.publishedAt}</p>
<p>{post.description}</p>
</div>
)}
</div>
);
}
render() {
return (
<div>
<h1>Top Stories</h1>
{this.state.loading ?
this.renderLoading()
: this.renderPosts()}
</div>
);
}
}
var First1 = React.createClass({
myClick: function(e){
alert(e.currentTarget.getAttribute("data-city"));
var city = e.currentTarget.getAttribute("data-city");
//alert('Show 1');
axios.get('https://newsapi.org/v1/articles?source=techcrunch&&sortBy='+city+'&apiKey=789ea3cd651a49e5ba9fc2061d68138f')
.then(res => {
//console.log(res.data);
// Transform the raw data by extracting the nested posts
const posts = res.data.articles;
//console.log(posts);
// Update state to trigger a re-render.
// Clear any errors, and turn off the loading indiciator.
//console.log(posts);
this.setState({
posts,
loading: false,
error: null
});
//console.log(this.setState);
})
.catch(err => {
// Something went wrong. Save the error in state and re-render.
this.setState({
loading: false,
error: err
});
});
},
render: function() {
return (<div>
<a onClick={this.myClick} data-city="latest"> Latest</a>
<a onClick={this.myClick} data-city="top"> Top</a>
</div>
);
}
});
// Change the subreddit to anything you like
ReactDOM.render(
<FetchDemo subreddit="reactjs"/>,
document.getElementById('root')
);
Here is link https://jsfiddle.net/69z2wepo/74393/
Issue is first time you are setting the data in parent component, and second time setting the data in child component, you need to update the state of parent component on click of top and latest.
Solution:
Pass a function from parent component and use that function to update the state once you get the response in child component, like this:
In Parent Component:
<First1 _updateState={this._updateState.bind(this)}/>
_updateState(posts){
this.setState({
posts,
loading: false,
error: null
});
}
In Child Component:
myClick: function(e){
....
.then(res => {
this.props._updateState(res.data.articles) //pass data to parent component
})
....
},
Check the fiddle for working solution: https://jsfiddle.net/ndg24fqc/
Note: In 1st component you are using es6 and in 2nd component you are using es5, try to use one thing either es6 or es5.

Reactjs this.setState is not a function error

Im novice to React js, i don't know whats wrong with below code, but i'm getting setState is not a function error.Please help me to fix this.
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
showProfile(){
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide} />
</header>
</div>
);
}
}
export default AppBarLayout;
You need to bind this.showProfile in the component constructor
this.showProfile = this.showProfile.bind(this)
More detail about this on the Handling Events page of the React doc : https://facebook.github.io/react/docs/handling-events.html
Expanding on Delapouite's answer if you don't like to bind every function in the constructor you can use arrow functions to automatically bind to the correct context.
For example:
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
// Now showProfile is an arrow function
showProfile = () => {
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide}/>
</header>
</div>
);
}
}
export default AppBarLayout;
In my case, I solved the problem without binding.
Declaring the method like this was generating the error:
async onSubmit(e) {
event.preventDefault();
this.setState({ shopEthereumAddress: e.target.id });
}
The CORRECT declaration which will not generate the error is this:
onSubmit = async event => {
event.preventDefault();
this.setState({ shopEthereumAddress: event.target.id });
}
This works.
toggleSwitch() {
this.setState({
name: 'Ram ji'
});
}
Using an arrow function keeps the context of this set to the parent scope. The main benifit of arrow functions apart from being more concise is
Main benefit: No binding of ‘this’
// use this arrow function instead of
toggleSwitch = () => {
this.setState({
name: 'Ram ji' //It's working
});
}

ReactJs Calling parent function

I am sorry if it is duplicate. I am passing parent function to child but when I use this method in child it give me this error
_this2.props.changeAppMode is not a function
I tried stackover flow already answered questions but can't able to resolve it. I am a newbie so might be I am missing some other concept
Following are my components
Parent Component
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
currentMode: 'read',
userId: null
};
this.changeAppMode = this.changeAppMode.bind(this);
}
changeAppMode(newMode, userId) {
this.setState({currentMode: newMode});
if (userId !== undefined) {
this.setState({userId: userId});
}
}
render() {
var modeComponent =
<ReadUserComponent
changeAppMode={this.changeAppMode}/>;
switch (this.state.currentMode) {
case 'read':
break;
case 'readOne':
modeComponent = <ViewUser />;
break;
default:
break;
}
return modeComponent;
}
}
export default Users;
Child
class ReadUserComponent extends React.Component {
constructor(props) {
super(props);
console.log(props);
};
componentDidMount() {
this.props.fetchUsers();
}
render(){
const users = this.props.users;
return (
<div className='overflow-hidden'>
<h1>Users List </h1>
<TopActionsComponent changeAppMode={this.props.changeAppMode} />
<UsersTable
users={users}
changeAppMode={this.props.changeAppMode} />
</div>
);
}
}
ReadUserComponent.propTypes = {
users: React.PropTypes.array.isRequired,
fetchUsers: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return {
users: state.users
}
}
export default connect(mapStateToProps, { fetchUsers })(ReadUserComponent);
Child of Child [This component calling parent function]
class TopActionsComponent extends React.Component {
render() {
return (
<div>
<a href='#'
onClick={() => this.props.changeAppMode('create')}
className='btn btn-primary margin-bottom-1em'> Create product
</a>
</div>
);
}
}
export default TopActionsComponent;
Thanking you in anticipation. Really appreciate your help
Sorry if it is duplicate but I am kind of a stuck in it
I think it's related to binding in child component. Could you try below piece of code while passing props into child component.
<TopActionsComponent changeAppMode={::this.props.changeAppMode} />
okay. try this. I think it should work.
<ReadUserComponent changeAppMode={() => this.changeAppMode}/>;
<UsersTable
users={users}
changeAppMode={() => this.props.changeAppMode} />
Try this, it solved the same issue i was facing.
In ReadUserComponent Component:
<TopActionsComponent changeAppMode={this.changeAppMode.bind(this)} />
Define this function in ReadUserComponent:
changeAppMode(type){
this.props.changeAppMode(type);
}
In TopActionsComponent Component:
<a href='#' onClick={this.changeAppMode.bind(this,'create')}
className='btn btn-primary margin-bottom-1em'> Create product
</a>
Define this function in TopActionsComponent Component:
changeAppMode(type){
this.props.changeAppMode(type);
}

Resources