React Component JSON Callback Scope - javascript

I'm using generator-react-webpack to create a React web app. This web app relies on JSON feeds - one of which is hosted on a CDN that does not support JSONP and the CDN url is a subdomain of the webapp. Is there any way to return the JSON data from within the React Component?
Basic React Component:
var AppComponent = React.createClass({
loadData: function() {
jQuery.getJSON(jsonFile.json?callback=?)
.done(function(data) {
console.log(data);
}.bind(this));
},
render: function(){
return ( ... );
}
});
I've tried a few solutions, and have come to the conclusion that I need to define my own callback on the JSON file like so:
JSON:
handleData({
"data": "hello World"
})
Is there a way for the handleData callback to be defined in the react component, or the response accessed from the react component? Any thoughts as to how I can get this to work are much appreciated. Thanks!

This looks like an odd way to do things, especially the part where you're using jQuery. That's a client-side utility to overcome not knowing where everything is and not having direct access to your elements. It makes no sense to use it when you're using React weith Webpack for bundling: React already knows where everything is (using refs) and Webpack means you can just use regular universal Node modules for everything that you need to do.
I'd recommend using something like, using request or a similar universal fetch API:
// loadData.js
var request = require('request');
var loadData = function(urlYouNeed, handler) {
request(urlYouNeed, function(error, response, body) {
if (error) {
return handler(error, false);
}
// do anything processing you need on the body,
var data = process(body);
handler(false, data);
};
So: just a module you can require in any component you define with require('./loadData'). And then in your actual component you do this:
var loadData = require('./loadData');
var AppComponent = React.createClass({
getDefaultProps: function() {
return {
jsonURL: "cdn://whateverjson.json"
};
},
getInitialState: function() {
loadData(this.props.jsonURL, this.updateData);
return {
data: []
}
},
updateData: function(err, data) {
if (err) {
return console.error(err);
}
data = secondaryEnsureRightFormat(data);
this.setState({ data: data });
},
render: function(){
var actualThings = this.state.data.map((entry, pos) => {
return <Whatever content={entry} key={entry.dontUseThePosVariableUpThere}/>
});
return (
<div>
...
{actualThings}
...
</div>
);
}
});
Much cleaner.

If I understand correctly the question, you only have to change your loadData this way :
loadData: function() {
var c = this
jQuery.getJSON(jsonFile.json?callback=?)
.done(function(data) {
c.handleData(data)
});
},
handleData: function(data) {
/* Implement here the function to handle the data */
},

Related

Vue prefetch data from separate backend

I have some queries from an API-Server that returns a json object that will be static over a user session, but not static forever.
It's a one-pager with Vue router.
How can I achieve that I:
can access this.myGlobals (or similar eg window.myGlobals) in all components, where my prefetched json-data from API-Server is stored.
My approach that is already working is to embed help.js via a mixin.
Oddly enough, I get hundreds of calls to this query. At first I thought that it only happened in the frontend and is chached, but the requests are actually sent hundreds of times to the server. I think it is a mistake of my thinking, or a systematic mistake.
i think the problem is, that the helper.js is not static living on the vue instance
main.js:
import helpers from './helpers'
Vue.mixin(helpers)
helpers.js:
export default {
data: function () {
return {
globals: {},
}
}, methods: {
//some global helper funktions
},
}, mounted() {
let url1 = window.datahost + "/myDataToStore"
this.$http.get(url1).then(response => {
console.log("call")
this.globals.myData = response.data
});
}
}
log in console:
call
SomeOtherStuff
(31) call
SomeOtherStuff
(2) call
....
log on server:
call
call
call (pew pew)
My next idea would be to learn vuex, but since its a easy problem, im not sure if i really need that bomb ?
You can use plugin to achieve this.
// my-plugin.js
export default {
install (Vue, options) {
// start fetching data right after install
let url1 = window.datahost + "/myDataToStore"
let myData
Vue.$http.get(url1).then(response => {
console.log("call")
myData = response.data
})
// inject via global mixin
Vue.mixin({
computed: {
myData () {
return myData
}
}
})
// or inject via instance property
Vue.prototype.$myData = myData
// or if you want to wait until myData is available
Vue.prototype.$myData = Vue.$http.get(url1)
.then(response => {
console.log("call")
myData = response.data
})
}
}
and use it:
Vue.use(VueResource)
Vue.use(myPlugin)

Method not working from created hook Vue.js

I am trying to create a web app based on a database. Setup: NodeJS and a Vuejs 2 app generated with the CLI (with Webpack). Currently, I am using axios to retrieve records into an object. Based on that object I want to draw some svg lines from certain points to other points. The method works completely as designed when running it from an #click (v-on directive). However, when I try to add it to the created hook it doesn't work. No errors displayed. It's just not running. Does anyone no why? Code example below.
<template>
<div class="holder">
<step v-for="item in steps"></step>
<events v-for="point in points"></events>
<button #click= "createArrows">Test</button>
</div>
</template>
<script>
import axios from 'axios'
import Step from './Step.vue'
import Events from './Events.vue'
export default {
name: 'Graph',
data () {
return {
steps: '',
events: '',
points: []
},
components: {
Step, Events
},
methods: {
getSteps: function() {
let getsteps = this
axios.get('localhost:8000/api/steps')
.then(function (response) {
getsteps.steps = response.data
})
.catch(function (error) {
getsteps.steps = "Invalid request"
})
},
getEvents: function() {
let getevents = this
axios.get('localhost:8000/api/events')
.then(function (response) {
getevents.events = response.data
})
.catch(function (error) {
getevents.events = "Invalid request"
})
},
createArrows: function() {
},
created() {
this.getSteps(),
this.getEvents(),
this.createArrows()
}
}
EDIT: Promises are already included in the axios library. Since I am new to this concept I missed this one. Refactored code below:
methods: {
getData: function() {
let getdata = this
axios.all([
axios.get('localhost:8000/api/steps'),
axios.get('localhost:8000/api/events')
])
.then(axios.spread(function (stepResponse, eventResponse) {
console.log('success')
getdata.steps = stepResponse.data
getdata.events = eventResponse.data
getdata.createArrows()
}))
.catch(function (error) {
console.log("Invalid request")
})
},
createArrows: function() {
}
},
created() {
this.getData()
}
}
</script>
I think it's a classic async issue.
With v-on, your call to createArrows is "timewise after" getSteps and getEvents: meaning that getSteps and getEvents have finished executing their internal ajax promises, have populated the relevant data into the component instance for createArrows to find and access.
However, inside the created() hook, if you think about it, the calls fall through to createArrows() instantaneously (before the promisy things inside getSteps and getEvents have finished).
You'll have to refactor the call to createArrows inside created() as promise resolve for it work there correctly.

Vue JS v-for not iterating over array

Hi guys I am using Vue JS to try and loop through my data. Here is my whole JS file:
var contentful = require('contentful');
var client = contentful.createClient({
space: 'HIDDEN',
accessToken: 'HIDDEN'
});
Vue.component('careers', {
template: '<div><div v-for="career in careerData">{{ fields.jobDescription }}</div></div>',
data: function() {
return {
careerData: []
}
},
created: function() {
this.fetchData();
},
methods: {
fetchData: function() {
client.getEntries()
.then(function (entries) {
// log the title for all the entries that have it
entries.items.forEach(function (entry) {
if(entry.fields.jobTitle) {
this.careerData = entries.items;
}
})
});
}
}
});
var app = new Vue({
el: '#app'
});
I am using methods to access some data from Contentful, once it has grabbed the necessary data it is sent to my data object.
If I console.log(careerData); within my console the following data is returned:
So I'd expect if I used v-for within my template and tried iterating over careerData it would render correctly however on my front-end I am left with an empty div like so:
<div id="app"><div></div></div>
I am currently pulling my component into my HTML like so:
<div id="app">
<careers></careers>
</div>
No errors are displayed within my console, can you think of any reason this might be happening?
Thanks, Nick
Several problems I think. As #dfsq said, you should use a arrow function if you want to keep context (this).
fetchData: function() {
client.getEntries()
.then(entries => {
this.careerData = entries.items
});
}
Then you may replace {{fields.jobDescription}} by {{career.fields.jobDescription}}, as #unholysheep wrote.
It may work. If it does not, you could add a this.$forceUpdate(); right after this.fetchData();
Use arrow function in forEach callback so you don't loose context:
fetchData: function() {
client.getEntries()
.then(entries => {
this.careerData = entries.items
});
}

Why my test failed after adding didInsertElement to fetch json in ember?

I have a simple component that it's going to fetch data after the component is inserted. It was ok until I run my test. I got this error.
Assertion Failed: You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in a run
I understand that the component is fetching data asynchronously but I'm not sure how to solve that in my integration test.
Here's my code
export default Ember.Component.extend({
didInsertElement: function() {
let source = this.get('source');
let url = apiUrl + source;
Ember.$.getJSON(url).then(function(response) {
this.set('data', response.data);
}.bind(this));
},
// something else
};
And this is my test.
moduleForComponent('panel', 'Integration | Component | panel', {
integration: true,
beforeEach () {
this.render(hbs`{{panel}}`);
}
});
test('it has source dropdown', function(assert) {
assert.equal(this.$('select[name="Source"]').length, 1);
});
Without the fetching data bit, the test runs ok.
Try wrapping the getJSON inside an Ember run loop.
So something like this:
var self = this;
Ember.run(function(){
Ember.$.getJSON(url).then(function(response) {
self.set('data', response.data);
});
});
Create a new Promise and then set the response data in the success handler.
The promise returned by Em.$.getJSON and new Ember.RSVP.Promise are different.
let self = this;
let promise = new Ember.RSVP.Promise(function() {
return Ember.$.getJSON(url);
});
promise.then((json) => {
this.set('data', json);
});

Isomorphic React.js & iso-http

I'm building a react web application which I'd like to render both server side and client side. I've been working off isomorphic-react-template but I've used iso-http to make a query to my content server. My aim is to have the app when server-side query the content server directly and render the content to HTML; and to have the app when client-side to do a normal AJAX request for content.
Here's the code I'm using. It works great on the browser, but the server-side render doesn't include the data; I presume because the server-side render isn't waiting for the async http call to return before it compiles the HTML and sends it over:
componentDidMount: function() {
var id = this.getParams().id;
var classThis = this;
request
.get("http://content.example.com/things/" + id)
.end(function(response) {
response.body = JSON.parse(response.text);
if (response.ok) {
classThis.setState({ data: response.body });
} else {
classThis.setState({ data: null });
}
});
}
I know this is all fairly new stuff; but is there a known way to solve this problem, so that the server side renderer waits for certain async calls to complete before sending?
I've managed to get this working with react-async.
I've pulled out my async function like this so I can call it from componentDidMount and from the asynchronous getInitialStateAsync function that ReactAsync uses:
mixins: [ ReactAsync.Mixin ],
getInitialStateAsync: function(callback) {
this.getContent(function(state) {
callback(null, state)
}.bind(this))
},
componentDidMount: function() {
this.getContent(function(state) {
this.setState(state);
}.bind(this));
},
getContent: function(callback) {
var id = this.getParams().id;
request
.get("http://content.example.com/things/" + id)
.end(function(response) {
response.body = JSON.parse(response.text);
if (response.ok) {
callback({ error: {}, post: response.body })
} else {
callback({ post: {}, error: response.body });
}
});
}
Then in my server.jsx I'm rendering with the async functions:
ReactAsync.renderToStringAsync(<Handler />, function(err, markup) {
var html = React.renderToStaticMarkup(<Html title={title} markup={markup} />);
res.send('<!DOCTYPE html>' + html);
});
Obviously there is huge potential for cock up here (the whole page fails to render if the server isn't present) but this feels like the start of the right approach!

Categories