I'm trying to fetch a json and include it in my state.
Strangely when I launch my ajax request with axios inside a componentDidMount and I console.log the state updated inside the render method it returns an empty object, then the state updated.
Don't mind the "undefined"
I just want to remove this empty object.
How can I do ?
Here is the code :
componentDidMount() {
axios({
method: 'get',
baseURL: '/url',
headers: {
"foo": "bar",
"key": "tata"
},
timeout: 3000,
responseType: 'json'
}).then(response => {
const posts = response.data.blocks.map(post => {
if (typeof post.description !== 'undefined' && typeof post.description !== '') {
return post;
} else {
return;
}
});
this.setState({posts});
});
}
componentDidMount is called after your render method and hence what you get in your render method initially is the initial state that you have set in the constructor. Since post is supposed to be an array, you can initialise it as an empty array in the constructor like
constructor(props) {
super(props);
this.state ={
posts: []
}
}
Once your request in componentDidMount is complete, the state post is set an the component will re-render and hence the second time you will see the correct data.
Check this answer too
Use componentWillMount or componentDidMount lifecycle functions for async request in React
Related
I have a problems sending information from a Laravel controller to TypeScript (TS)
I have tried using Ajax but I have not been able to get the information sent from controller
Laravel controller
public function getUrl()
{
$Id = request('id');
$urlAdjunto = Adjunto::where('articulo_id', $Id)->plunk('url');
return response()->json(['url' => $urlAdjunto]);
}
Ajax function in typescript
function getUrl(id) {
var a= $.ajax({
type : 'GET',
dataType: 'json',
url : '/adjunto',
data : {id}
});
return a;
}
A few small modifications to make this type safe and return a promise as expected:
async function getUrl(id: number): Promise<any> {
return await $.ajax({
type: 'get',
dataType: 'json',
url: '/adjunto',
data: {id}
})
}
And now you have a promise to work with that you can observe and react to in your code:
getUrl(15)
.then((response: any) => {
//response.data has everything you need
}).catch((error: any) => {
// do something with failures
})
Or call it from another async function and leverage await for cleaner syntax:
async myFunc(){
try {
const { data } = await getUrl(15)
// do something with your data
} catch(error) {
// we all make mistakes
}
}
I've got a question regarding using Google's Invisible Recaptcha when using an AJAX form submission using Vue components.
I've created a VueJS component which I include in the following 'recaptcha button' component in:
<template>
<div>
<div v-if="failed" style="color: red;"><strong>Sorry, the captcha validation failed. Please try again.</strong></div>
<div :id="name"></div>
<button :class="classes" type="button" #click="validate()">
<slot>Submit</slot>
</button>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default: 'recaptcha',
required: false
},
classes: {
type: String,
required: false,
default: ''
},
},
data: function ()
{
return {
failed: false,
};
},
mounted: function ()
{
this.initReCaptcha();
},
methods: {
initReCaptcha: function() {
var self = this;
setTimeout(function() {
if(typeof grecaptcha === 'undefined') {
self.initReCaptcha();
}
else {
grecaptcha.render(self.name, {
sitekey: 'site-key-here',
size: 'invisible',
badge: 'inline',
callback: self.response
});
}
}, 100);
},
validate: function ()
{
grecaptcha.execute();
},
response: function (token)
{
this.$parent.fields['g-recaptcha-response'] = token;
this.$parent.submit();
}
},
}
</script>
As you can see, I'm assigning Google's Recaptcha's callback function to the current instance of the recaptcha component's 'response' function. However, when I submit one of the forms, it seems to be calling the response function of the other component on the page.. and therefore trying to call submit on a form that has no input in so far..
We thought it might be a case of the recaptcha rendering not actually creating two instances and therefore on the second one, the callback is just being overwritten, but from logging the components in the mounted function, the form that's being submitted instead of the one we're trying to submit is being instantiated first, which lead us to believe it's not a case of overwriting...
Any help on the matter would be much appreciated!
Cheers,
PM
Figured this out, did not know that the render function returned the widget ID. grecaptcha.execute() will just execute the first widget in the global list if not given an optional widget ID parameter.
To tackle this, assign the widget ID returned by the render function to a data property in the component and then within the validate function, call execute with that widget ID as the parameter.
self.widgetId = grecaptcha.render(self.name, {...});
validate: function ()
{
grecaptcha.execute(this.widgetId);
},
I am not getting state updated after making a AJAX call in componentDidMount. My api call is returning the correct data .
Any ideas if I am doing setState in wrong lifecycle component? Making the AJAX request in the componentDidMount and also setting the new state there. In the constructor I set the state to empty arrays
class DeliveryDateInput extends React.Component {
constructor(props) {
super(props);
this.getDeliveryDate = this.getDeliveryDate.bind(this);
this.state = {
selectDate: selectedDate || validDelDateArray[0],
validDeliveryDateArray: validDelDateArray,
firstDeliveryDay: [],
lastDeliveryDay: [],
selectedDate: [],
deliveryDatesAllowed: [],
offDays: [],
};
}
componentDidMount () {
console.log('App componentDidMount');
this.getDeliveryDate();
}
componentWillUpdate(nextProps, nextState) {
this.getDeliveryDate();
console.log('Your prev offday state: ' + this.state.offday);
console.log('Your next offday state: ' + nextState.offday);
}
getDeliveryDate() {
const self = this;
$.ajax({
type: 'GET',
url: //deliveryDateAPI,
contentType: 'application/json; charset=utf-8',
success(data) {
self.setState({
firstDeliveryDay: data.firstDeliveryDate,
lastDeliveryDay: data.lastDeliveryDay,
selectedDate: data.firstDeliveryDate,
deliveryDatesAllowed: data.deliveryDates,
offDays: data.offDays,
});
},
failure(errMsg) {
console.log(errMsg);
},
});
}
In React, you will have to bind ajax callback functions to the component (which is .bind(this))
You knew this and have done similar thing in your constructor, but for the jquery ajax, it will look a bit different like: (and the self variable is unnecessary here)
getDeliveryDate() {
$.ajax({
type: 'GET',
url: //deliveryDateAPI,
contentType: 'application/json; charset=utf-8',
success: function(data) {
this.setState({
firstDeliveryDay: data.firstDeliveryDate,
lastDeliveryDay: data.lastDeliveryDay,
selectedDate: data.firstDeliveryDate,
deliveryDatesAllowed: data.deliveryDates,
offDays: data.offDays,
});
}.bind(this),
error: function(errMsg) {
console.log(errMsg);
}.bind(this),
});
}
I've tested the above code and it works. You may not need to bind the error callback since you don't do anything yet to the component, but maybe it's still necessary if you want to make further actions there!
If this doesn't work yet, please comment here the errors on your console, thanks!
ADDED MODIFICATION:
Please remove the ajax call in your componentWillUpdate, which will create a "forever" loop:
componentWillUpdate(nextProps, nextState) {
//this.getDeliveryDate();
console.log('Your prev offday state: ' + this.state.offday);
console.log('Your next offday state: ' + nextState.offday);
}
The reason is: actually, after a state is set, componentWillUpdate() will be executed, and here if you run the ajax call again, after that it will setState again, then the loop will last forever!
I've been using the following code for doing ajax calls in Angular which worked fine so far:
function getData (url) {
var deferred = $q.defer();
$http.get(url, {
cache: true
}).success(function (data) {
deferred.resolve(data); // works
});
return deferred.promise;
}
As you can see there is only a success handler. When I wanted to add an error handler, I noticed that the docs state that success and error were depracated and should not be used any more (https://docs.angularjs.org/api/ng/service/$http). Instead the .then promise should be used, so I changed my code to this:
function getData (url) {
var deferred = $q.defer();
$http.get(url, {
cache: true
}).then(function (data) {
deferred.resolve(data); // Not called :(
}, function () {
deferred.resolve(false); // Not called :(
});
return deferred.promise;
}
Now everything stopped working. .then is never called. Why? I'm on latest Angular version 1.4
Ok, I found the problem. Actually, there were two problems.
First, I had a redundant promise which I removed:
function getData (url) {
return $http.get(url, {cache: true});
}
Second, the returned data from .then seems to be a little different from .success. Instead of returning the plain result data, it returns an object with some meta data from the call. So, in order to get the data, I'd have to access data.data:
pub.getList = function () {
return getData(serviceurl + "?c=latest").then(function (result) {
var list = result.data;
precacheArticles(list);
return list;
});
};
This example of using jquery-ui autocomplete with a remote web service has a nice concise syntax:
$( "#birds" ).autocomplete({
source: "search.php",
minLength: 2
});
Here search.php returns an array as follows:
[
{ "id": "Passer domesticus", "label": "House Sparrow", "value": "House Sparrow" },
...
]
I want to use a WCF web service, but the same syntax doesn't work because the array returned is wrapped in a 'd' container object:
{"d":
[
{ "id": "Passer domesticus", "label": "House Sparrow", "value": "House Sparrow" },
...
]
}
Of course I can get round this by writing code to look into the "d" container, something like the following (untested - could have typos):
$( "#birds" ).autocomplete({
minLength: 2
source: function (request, response) {
$.getJSON("search.svc/GetBirds", request, function (data, status, xhr) {
if (status == "success") response(data.d);
}
}
});
Is this the best I can do or is there some more concise syntax?
Ideally I'd like to be able to specify "source" as a url and have it work with responses that are returned with or without the "d" container.
In my mind you have two options.
The first is to create a helper function that will map the results for you. This is probably the best/easiest solution. Simple code:
$( "#birds" ).autocomplete({
minLength: 2
source: function (request, response) {
$.getJSON("search.svc/GetBirds", request, function (data, status, xhr) {
if (status == "success")
handleResponse(data); //you write this function
}
}
});
The second option is you can "monkeypatch" the AutoComplete plugin functions to override the default behavior.
So in your case you want to override the $.ui.autocomplete.prototype._initSource function. Fair warning here that you are basically overriding a core function in the UI library and if that library is ever updated your function will always override it.
// Create a closure so that we can define intermediary
// method pointers that don't collide with other items
// in the global name space.
function monkeyPatchAutocomplete() {
// don't really need this, but in case I did, I could store it and chain
var oldFn = $.ui.autocomplete.prototype._renderItem;
var requestIndex = 0;
$.ui.autocomplete.prototype._initSource = function() {
// whatever
console.log("Override method");
var self = this,
array, url;
if ($.isArray(this.options.source)) {
array = this.options.source;
this.source = function(request, response) {
response($.ui.autocomplete.filter(array, request.term));
};
} else if (typeof this.options.source === "string") {
url = this.options.source;
this.source = function(request, response) {
if (self.xhr) {
self.xhr.abort();
}
self.xhr = $.ajax({
url: url,
data: request,
dataType: "json",
autocompleteRequest: ++requestIndex,
success: function(data, status) {
console.log("Override success function, handling request");
if (this.autocompleteRequest === requestIndex) {
response(data); //you handle both types of data here
}
},
error: function() {
console.log("Override error function, handling request");
if (this.autocompleteRequest === requestIndex) {
response([]);
}
}
});
};
} else {
this.source = this.options.source;
}
};
}
// When DOM is ready, initialize.
$(document).ready(function() {
monkeyPatchAutocomplete();
$("#birds").autocomplete({
source: "http://jqueryui.com/demos/autocomplete/search.php",
minLength: 2
});
});
Then your code doesn't need to execute anything different, it just handles the differences and passes it along to the success method.
Here is a jsFiddle for this: http://jsfiddle.net/lemkepf/DAQ6s/5/ Note: the actual autocomplete wont work as the cross domain security is in place. You can open up firebug and see the console.debug lines fire when you start typing in the box.