I have two components BookInfo and AuthorLink. I want to fetch the book info and its author id from server and pass it to AuthorLink to use in componentDidMount to fetch the author name . However, the componentDidMount of AuthorLink run before the componentDidMount of BookInfo so authorid is null when the time it pass to AuthorLink.
How can I fetch the data and the data is ready before I pass it to the child?
var BookInfo = React.createClass({
.
.
.
componentDidMount: function() {
var id = this.props.params.id;
$.ajax({
url: '/api/books/' + id,
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error('#GET Error', status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<AuthorLink authorid={this.state.data.authorid} />
);
}
});
var AuthorLink = React.createClass({
.
.
.
componentDidMount: function() {
$.ajax({
url: '/api/author/' + this.props.authorid,
dataType: 'json',
success: function(data) {
this.setState({authorname:data.authorname});
}.bind(this),
error: function(xhr, status, err) {
console.log('#GET Error', status, err.toString());
}.bind(this)
});
}
},
render: function() {
return (
<a href='#' onClick={this.linkOnClick} >
{this.state.authorname}
</a>
);
}
});
componentDidMount is just ran once per component on the initial render, hence it wont work in this case when we have to wait for the authorID in the BookInfo component.
You would be better off using componentWillReceiveProps in the AuthorLink component to fetch the author data, which is ran before render when, obviously, new props are received in the component. A tip is to also handle the case where the authorname isn't set in the render function, which will most likely be the case on the first render.
More on lifecycle methods her: https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
You would have to move the ajax call into componentWillUpdate or componentWillReceiveProps and fire it when this.props.autherid changes (from null to an actual value)
Or you could delay the rendering of <AuthorLink/> itself by checking in the render if this.state.authorname is set:
render: function() {
var content = <div/>;
if(!!this.state.authorname) {
content = <a href='#' onClick={this.linkOnClick} >
{this.state.authorname}
</a>;
return content;
}
Related
I am using a service with an async call.
The service looks like that;
var routesApp = angular.module('routesApp', []);
routesApp.factory('angRoutes', function($http) {
var angRoutes = {
async: function(id) {
var data = $.param({
query: id
});
var config = {
headers : {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
}
}
var promise = $http.post('../ajax-php.php', data, config)
.success(function (data, status, headers, config) {
return data;
console.log(data);
})
.error(function (data, status, header, config) {
});
return promise;
}
};
return angRoutes;
});
When the page first load I use one controller to fill the scope;
routesApp.controller('topRoutesCtrl', function topRoutesCtrl($scope,$http, angRoutes) {
angRoutes.async('top').then(function(data) {
$scope.angRoutes = data;
console.log(data);
});
});
This is all working great. But then I use another controller for when the user click on something.
routesApp.controller('navRoutesCtrl', function navRoutesCtrl($scope,$http, angRoutes) {
$scope.update = function(id) {
angRoutes.async(id).then(function(data) {
$scope.angRoutes = data;
console.log(data);
});
}
I am able to see the data I am getting in the console and the id does get passed in the update function and the data is corect but it seams that my scope is not getting updated. it remains the value that was first sent when the page load.
How do I update my scope?
UPDATE
as seen here
In my angular HTML I do a ng-repeat like this
<div ng-controller="topRoutesCtrl" class="ajax">
<div id="ajaxLoader"> <img class="loadingGif" src =" /images/ajax-loader.gif"> </div>
<div data-ng-repeat="r in angRoutes.data" class="routes-block" >
{{r.name}}
</div>
</div>
Now in my angular JS if I do
routesApp.controller('topRoutesCtrl', function topRoutesCtrl($scope,$http, angRoutes) {
angRoutes.async('top').then(function(data) {
$scope.angRoutes = data;
console.log(angRoutes);
});
});
Note that in the console I can see the same thing if I console.log(data) or console.log(angRoutes) My app however will only work if I do $scope.angRoutes = data; and nothing gets displayed if I do $scope.angRoutes = angRoutes;
So maybe I am using the referencve the wrong way in my ng-repeat
you can use wrapper $timeout for manual start $digest cicle
$scope.update = function(id) {
angRoutes.async(id).then(function(data) {
$timeout(function() {
$scope.angRoutes = data;
}
console.log(data);
});
}
but keep in mind that $digest cicle are triggerd automaticly after $http calls and written behavior is strange
I am searching a lot about this topic but can’t seem to find a answer that clarifies me.
I am trying to make my first react app. I have started with create react app to understand the basics and then moved my app to server side rendering. As I was developing I got in a question. How can I fetch some data from my API before the app gives a answer (server side) so I can put some stuff in there that I really need?
You could use an ajax request to pull the data from API and set that to the state variable of react. So that the state variable will be used during render. You could define a method for this and, you can call that method from componentDidMount() to set the values to the state variable.
var CommentBox = React.createClass({
getInitialState: function() {
return {data : []};
},
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(comment){
var comments = this.state.data;
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
console.log(data);
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer(), this.props.pollInterval);
},
render: function() {
return (
<div className='commentBox'>
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
In the above code sample, I wrote a method loadCommentsFromServer() specifically to pull data from an API. And I call that from componentDidMount() and stored the output from API into state variable. And, I used the state variable during render.
Note: The above code sample may not work on its own. Since, it's dependencies are not resolved.
I have a few sliders in an angularJS app. What I try to achieve is when the user change the option on a select dropdown, the sliders should get an update with new values from a RestAPI.
This example is for one of the sliders, but the rest are the same.
Creating the slider on controller
myCtrl.ageSlider = {
value: 17,
options: {
showSelectionBar: true,
ceil: 38,
hideLimitLabels: true,
floor: 17,
onChange: myCtrl.getResults,
getPointerColor: function(value) {
return '#FFCF00'
},
getSelectionBarColor: function(value) {
return '#FFCF00'
}
}
};
The update function on controller which is called on ng-change of the select
myCtrl.updateSliders = function () {
//other sliders here that don't need a call on API
StaffServices.getMedic(myCtrl.selected.id,
function(response) {
myCtrl.ageSlider.value = parseInt(response.data.age);
myCtrl.getResults();
},
function(response) {
console.log('Something went wrong on medic process');
});
}
And the getResults() function which call a service
myCtrl.getResults = function() {
myCtrl.results = myService.myUpdatesCalc(passSomeValues);
}
When I manually change the slider from the user interface, the onChange fires the getResults function. Spending hours on this and cannot find the reason. Any help?
Edit: This is the service getMedic to avoid any confusion
service.getMedic = function(id, onSuccess, onError) {
$http.get(API_Base + 'api/staff/medic?id='+id,
{
headers: { 'Authorization': 'Bearer '+$cookies.get('token')}
}).
then(function(response) {
onSuccess(response);
}, function(response) {
onError(response);
});
}
Angular $http return promises, and hence your values are not updated. You will need to handle the promises, and then update it at the callback function:
myCtrl.getResults = function() {
myService.myUpdatesCalc(passSomeValues) //this is your REST API, will return a promise
.then(function(response) { //this is the callback function to handle the successful ajax
myCtrl.results = response.data; //update your results here!
}, function(error) { //this is the callback function to handle the failed ajax
console.log('error')
})
}
I'm trying to change the src attribute of an element after an AJAX call to a database. I have a default image url defined in getDefaultProps(), but after the AJAX call the image doesn't change.
PictureWidget is a child component of a Section component that controls the state (it also passes dataSource and dataSourceParams to PictureWidget). I'm not sure if I can use a local state for PictureWidget so I'm trying to do it through props.
This is my code:
var PictureWidget = React.createClass({
getDefaultProps: function() {
return {
url: 'https://d2o57arp16h0eu.cloudfront.net/echo/img/no_image_available.png'
}
},
componentDidMount: function() {
this.componentDidUpdate();
},
componentDidUpdate: function() {
// Grab img URL from DB
var postData = {
dataSource: this.props.params.dataSource,
dataSourceParams: this.props.dataSourceParams
};
$.ajax({
type: 'POST',
url: ajax_endpoint,
cache: false,
data: postData,
dataType: 'json',
success: function(response) {
if (response.data.length > 0) {
this.updateImage(response.data[0][0]);
}
}.bind(this),
error: function(response) {
this.render();
}
});
},
updateImage: function(url) {
console.log("Updating props.url with " + url);
this.props.url = url;
this.render();
},
render: function(imageURL) {
console.log("Rendering img " + this.props.url);
return React.createElement('div', {className: ' pure-u-' + this.props.columns},
React.createElement('div', {className: 'picture-widget'},
React.createElement('img', {src: this.props.url})
)
)
}
});
And this is my console log (forgive me for the poor formatting, still new to Overflow):
Rendering img https://d2o57arp16h0eu.cloudfront.net/echo/img/no_image_available.png
Updating props.url with http://vignette3.wikia.nocookie.net/animalcrossing/images/4/49/Tumblr_lvrcmvCpsS1qbeyouo1_500.jpg/revision/latest
Rendering img http://vignette3.wikia.nocookie.net/animalcrossing/images/4/49/Tumblr_lvrcmvCpsS1qbeyouo1_500.jpg/revision/latest
The initial render() grabs the default URL, but after the AJAX call this.props.url does get updated to the new value, so I would suspect React.createElement('img', {src: this.props.url}) is the trouble maker. Can I not update the src attribute this way?
This is what state is for. Try using getInitialState instead of getDefaultProps and bind url to this.state using setState().
getInitialState: function() {
return {
url: 'https://d2o57arp16h0eu.cloudfront.net/echo/img/no_image_available.png'
};
},
updateImage: function(url) {
console.log("Updating state.url with " + url);
this.setState({ url: url });
}
I have built somewhat of a login and I want the user to be kicked to the dashboard page upon successful login. As of now, everything works as expected except for the model doesn't update until I reload the Dashboard page.
I wrote a service to handle the $http requests:
abcApp.service('Login', function($q, $http){
return({
login:login
})
var headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
function login(login){
var formVals = {
username: login.username.$modelValue,
password: login.password.$modelValue,
remember: login.remember.$modelValue,
}
var request = $http.post('assets/php/do_login.php', formVals, null, headers);
return( request.then( handleSuccess, handleError ) );
}
function handleSuccess( response ) {
return( response.data );
}
function handleError( response ) {
if (
! angular.isObject( response.data ) ||
! response.data.message
) {
return( $q.reject( "An unknown error occurred." ) );
}
// Otherwise, use expected error message.
return( $q.reject( response.data.message ) );
}
});
Here is the controller that should handle logging in:
$scope.doLogin = function (){
Login.login($scope.login_form)
.then(function(data){
if(data.status == 'success'){
$scope.currentUser = data.user;
$scope.msg = 'Welcome '+data.user.realname;
$scope.userId = data.user.id;
$scope.loggedIn = true;
}
}).then(function () {
return $timeout(function () {
$state.go(
'dashboard',
{},
{reload: true}
);
}, 250);
});
};
Originally I had this all in a controller, then I split it into a service and controller. I have tried $scope.$apply, but I get an inprog error. How can I get the model to update without reloading?
Your controller and the variables in it will be served from the cache.
There are a few ways you can solve this. First is disable caching in the state definition...
$stateProvider.
state('dashboard', {
url: '/dashboard',
cache: false,
... but I have had situations where that doesn't work. You can also set a trigger on the state change (either $stateChangeStart on the login controller or $stateChangeSuccess on the dashboard controller):
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
// reload stuff
});
You can also set these triggers on the $rootScope if you want them to fire at state changes through all your controllers.