Here's my problem. I'm trying to get some data (latitude/longitude) from a public API through an AJAX call (with Axios) in a React app.
User fills address & postcode inputs, their values are passed as parameters in the AJAX fetchCoordinates() request function, and as soon as it gets back some data, it should update latitude/longitude state with the first result's coordinates.
User can update the address and postcode inputs, and the app should consequently update coordinates state.
Here's the problematic method code :
componentDidUpdate(prevState) {
fetchCoordinates(this.state.address, this.state.postcode)
.then((data) => {
if (typeof(data) === "object" && data.features.length) {
this.setState(function() {
return {
latitude: data.features[0].geometry.coordinates[1],
longitude: data.features[0].geometry.coordinates[0]
}
});
}
})
}
Actually, I can get the coordinates back and even render them onscreen, but as soon as I get the coordinates back, AJAX request can't stop looping, again and again...
I'm a React beginner, but that looks a suspicious behavior !
If someone had an idea, it would be kind. Thx in advance for the help.
Silvère
this.setState should not call a function
this.setState(function() { // remove the function piece here
return {
latitude: data.features[0].geometry.coordinates[1],
longitude: data.features[0].geometry.coordinates[0]
}
})
Should read
this.setState({
latitude: data.features[0].geometry.coordinates[1],
longitude: data.features[0].geometry.coordinates[0]
})
As mentioned - you should put this code in ComponentDidMount or ComponentWillMount - not ComponentDidUpdate
Related
I'm using the following code to test a state-dependent react component using jest and rtl:
test("render author, date and image correctly after going next post", async () => {
const store = configureStore({
reducer: {
data: dataReducer
}
});
const Wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
render(<Post />, { wrapper: Wrapper });
const getSpy = jest.spyOn(axios, 'get').mockReturnValue(mockPostJSON);
await store.dispatch(FETCH_POSTS());
expect(getSpy).toBeCalledWith('https://www.reddit.com/r/EarthPorn/.json');
const beforeClick = await screen.findByTestId('authorAndDate');
expect(beforeClick.innerHTML.toString()).toBe(mockPostsList[0].author + ' - ' + mockPostsList[0].date);
fireEvent.click(screen.getByText('Next post'));
const afterClick = await screen.findByTestId('authorAndDate');
expect(afterClick.innerHTML.toString()).toBe(mockPostsList[1].author + ' - ' + mockPostsList[1].date);
})
The problem I'm having is that before the click everything in the store is set up correctly and the authorAndDate element displays the first item in the array of posts. But after the click is fired the store goes back to the initial state it had before loading the mock data. I checked within the component's event handler and right before it does anything the state has been reset. The code is as follows:
const handleNextClick = () => {
store.dispatch(GO_NEXT_POST());
store.dispatch(FETCH_COMMENTS());
}
I've been an hour over the code trying to find something that would reset the state and found nothing. I'm guessing it's some kind of interaction between jest and rtl but I can't figure out why the store in the test has one state and the store in the component's event handler has another :S
Well, figured it out. Can't use store.dispatch directly as it's accessing a stale state. Needed to use the useDispatch hook. Hope this serves anybody who faces the same problem in the future.
In my application, I am using actions to do all of my ajax calls. When the results come back, it dispatches them to the reducer, which then puts it in the store. My component is bound to the property and will then be able to get it from the store.
However, I am having an issue trying to figure out the best way to do form submissions. From a listing page, a user can click on a link from any row to open a modal. This modal has a form in it. When the form is filled out, it will then pass the data along to an action, which will submit it. The only response from a valid submission is a HTTP 200.
Without using callbacks, how would the modal know that the ajax call is complete, so it can close itself? As of now, I have a flag in the store called form.processing. This is default to false, and the action will set it to true when it begins and false when its done. The component watches this and then knows when it goes from true to false and knows everything is done. However, I feel like there should be a better way.
Or should I be using callback in forms, even though we don't follow that process for any other ajax call?
Here are following pseudo code can help you:
constructor () {
this.state = {
disaplyModalPopup: false;
}
}
handleSubmit = () => {
this.setState({disaplyModalPopup: true})
let payLoad = { 'Key':this.state.something }
this.props.hitAPI(payLoad).then((res) => {
if (res.data.success) {
this.setState({
'disaplyModalPopup': false
})
}else{
this.setState({
'disaplyModalPopup': true,
'errorMessage': 'something wend wrong'
})
}
})
}
rendor (){
let errorMessage = {this.state.errorMessage}
let disaplyModalPopup = {this.state.disaplyModalPopup}
return (
{disaplyModalPopup ? <modal> </modal> : ''}
{ errorMessage? 'errorMessage': ''}
)
}
Here I have handled your modalPopup with disaplyModalPopup state.
And After get in the response saved in reducer, it is changes as {disaplyModalPopup: false}
And modalPopUp HTML will disappear.
But in case your API get in failed while making response.
So that case: i have handle as error message in as text
errorMessage where you can show your error message. So that Modal is closed side by side.
I need to update the context after an ajax request has finished. I'm using the flux architecture and everything works to the point that when my component is notified about the updated I need to set the new context.
A simple demostration:
I have a parent component which generates the context by calling a store. The store gets the data after an ajax request is initialized somewhere else. Like this:
RowAPI.ajaxGetAllRows();
Then I have my component which holds the context:
let ParentComponent = React.createClass({
childContextTypes: {
rows: React.PropTypes.object
},
getChildContext: function() {
return {
rows: RowStore.getAllRows(),
};
},
componentDidMount: function() {
RowStore.addChangeListener(this._onRowsChanged);
},
componentWillUnmount: function() {
RowStore.removeChangeListener(this._onRowsChanged);
},
render() {
return (
<ChildComponent />
);
},
_onRowsChanged: function() {
//Now we need to update context
}
});
Now since we are listening for row changes, we will get an update when our ajax request has finished and put the data into our store. Now we need to get that data and set it as context. That is the problem.
This is my child component that uses the context. I know that I just can pass the rows as a props to my child but this is just an example and in my real scenario I have many children which would need to pass the props.
let ChildComponent = React.createClass({
contextTypes: {
rows: React.PropTypes.object
},
render() {
return (
<div style={styles.wrapper}>
{this.context.rows}
</div>
);
},
});
Thanks in advance!
I would change the getChildContext in ParentComponent to refer to the state instead of a function call to the RowStore.
getChildContext: function() {
return {
rows: this.state.rows,
};
}
Then, whenever a row changes, and the _onRowsChanged callback it called, it can set this.state.rows accordingly.
I believe that the issue with the original method of calling RowStore.getAllRows() inside getChildContext is that it is only called once. Nothing is forcing it to call RowStore.getAllRows() on every change.
However, by using a state, you can use Flux concepts to "force" a change in state on every update, and that will be reflected in the context.
I'm trying to add a clientEvent filter to an already working AJAX fullCalendar. The idea is to allow the visitor to filter the events already displayed by selecting a choice in a droping list.
The code is currently as follows:
jQuery(document).ready(function($) {
$('#calendar').fullCalendar({
events: function(start, end, timezone, callback) {
$.post(
MyAjax.ajaxurl,
{
action: 'get_fullcalendar',
data: {
slotbegin: start.unix(), // données à compléter
slotend: end.unix()
}
},
function( events ) {
callback( events );
}
);
},
eventRender: function(event, element) {
element.qtip({
id: 'eventdetails',
content: {
text: event.image + event.description,
title: event.title
},
});
}
});
$("#cible_select").change(function() {
var cible = $(this).val()
var events = $('#calendar').fullCalendar('clientEvents', function(evt) {
return evt.public_cible == cible;
});
});
});
The fullCalendar works OK by itself. But I don't know how to integrate the clientEvents bit so it is used when the user makes change to the #cible_select selector.
I've been trying many things for the past hours, and would appreciate some help to solve this issue.
Thanks a lot for any hint.
This function might help you. call this function where ever you want.
function parseClientEvents(/*pass params here*/){
var clientArr = $('#calendar').fullCalendar('clientEvents');
for(i in clientArr){
console.log(clientArr[i]);
//all your logic goes here.
}
return true;
}
I seem to have misunderstood the way clientEvents works. I thought it would re-display the whole calendar with the selected events only, but that's not the case.
removeEvents works to hide/suppress events one doesn't want any more, but those are not available on the client side any more, so refetchEvents has to be used if the user changes his mind and makes another choice.
removeEventSource works only if you have a limited number of sources, and I want to be able to combine several filters, so there is quite a number of combinations.
So, I'm completely rethinking my filtering strategy: clientEvents is definitely not the way to toggle on/off events on a multicriteria basis.
I'm having an issue here when attempting to build an integration to our partners. They're gonna submit an image URL as a GET-variable, which I obviously don't want to print straight up. The submitted image URL is submitted back to our servers with AJAX to be sanitized, returned and then updated.
What I want to do here is when the model loads, I want to display a placeholder image, and when the sanitation check is done by the server, it will return the URL (the same or another placeholder) that is to be set as the template image source.
Now, the problem is that I don't get how to make Ember listen for the update of this event. I'm trying to use observes, but apparently, this isn't available in the route. Here's my current code:
ROUTE
MyApp.PartnerRoute = Ember.Route.extend({
imageUrl: "/img/placeholder.png";
getImageUrl: function(imageUrlToCheck) {
instance = this;
$.ajax({
url: "/ajax/get-image-url",
type: "post",
data: {
"imageUrl": imageUrlToCheck
},
success: function(response) {
if(response.status === 0) {
instance.set("imageUrl", response.data.imageUrl);
}
}
});
},
// Ember update property.
imageUrlDidChange: function() {
this.get("imageUrl");
}.observes("imageUrl"),
model: function(params) {
this.getImageUrl(params.imageUrl);
return {
heading: "Welcome!",
imageUrl: this.imageUrl
}
}
});
VIEW
<script type="text/x-handlebars" data-template-name="partner">
<h1>{{heading}}</h1>
<img {{bind-attr src=imageUrl}} />
</script>
I get the error message:
Uncaught TypeError: Object function () {
this.get("imageUrl");
} has no method 'observes'
I'm not at all sure as of how to make this happen. Am I going about this the wrong way? Any help is appreciated.
Thank you for your time.
Best regards,
dimhoLt
PS. I've extracted the applicable pieces of code from much bigger objects, so if there are any typos, missing commas etc, it's due to the copy-paste and is not applicable to the actual code.
EDIT:
Worth noting is that because of legacy functionality I haven't yet rewritten, I was forced to turn off Ember extended prototypes. This is, I guess, the major cause of the issue.
Since I wrote this, I've also gone over to using a fixed model instead of attempting to work directly with the route.
You need to use a setter
http://emberjs.jsbin.com/OxIDiVU/117/edit
model: function(params) {
var model = {
heading: "Welcome!",
imageUrl: this.imageUrl
};
this.getImageUrl(params.imageUrl).then(function(result){
Em.set(model, 'imageUrl', result.imageUrl);
});
return model;
}