Using React to call Nested API? - javascript

Say I had the following JSON file:
{
"farmer": [
{
"crops": "corn"
}
],
"activities":{
"hobbies": "swimming"
},
"name: Todd"
}
I would like to know how to make calls to them using React. My best attempt is as shown below.
componentDidMount: function(){
var selfish = this;
$.get('~~someurl~~', function(data){
selfish.setState(data);
});
},
render: function(){
return (<div>
<p>{this.state.name}</p>
<p>{this.state.activities.hobbies}</p>
<p>{this.state.farmer[0].crops}</p>
</div>)
}
I get that the first one {this.state.name} will run as it has been written. However, I am uncertain as to how to express the final two {this.state.activities.hobbies} and {this.state.farmer[0].crops} using the React syntax.
I have written them out in hopefully a way that expresses what I am trying to achieve with each call.
EDIT: The specific error that results from the latter two state that they are undefined.
EDIT: So I got the second one working by adding an empty initial state.
getInitialState: function(){
return {activities: '', farmer: ''}
}
However, this leaves the last one, which still returns an error.

The problem is that you are using componentDidMount when you should use componentWillMount. Check out the documentation on these lifecycle methods.
This is what the documentation says about componentDidMount
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs.
Which means when you first render your states are not declared unless you have used getInitialState before (another lifecycle method).
I simply did this:
componentWillMount: function () {
this.setState({
"farmer": [
{
"crops": "corn"
}
],
"activities":{
"hobbies": "swimming"
},
"name": "Todd"
});
},
and I was able to use this.state.farmer[0].crops in my render method
EDIT:
To be clear. If you need to retrieve the data after you rendered the component then you need to specify default values for the states that you use in render. This can be achieved using getInitialState:
getInitialState: function () {
return {
"farmer": [
{
"crops": DEFAULT_VALUE
}
],
"activities":{
"hobbies": DEFAULT_VALUE
},
"name": DEFAULT_VALUE
});
},

Related

Vue 2 watch fails for mixin data property, but works if the component has the data property

I am trying to move some functionality to a vue mixin from the component, to be able to use it in multiple components.
This (simplified version of the code) works:
export default {
data() {
return {
file: {},
audioPlayer: {
sourceFile: null,
},
};
},
watch: {
'audioPlayer.SourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
}
But if I move the audioPlayer data object to a mixin, the watch does no longer fire.
Is this expected behavior?
N.b. I resolved this by directly making the 'file' data property into a computed value, which works in this particular case, but the behavior is still strange.
You need a lowercase s. sourceFile not SourceFile
watch: {
'audioPlayer.sourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}

VueJS Watcher is not triggered while watching deep object changes

I got a codesandbox that reproduces my problem: codesandbox example watcher not triggering.
I am working on a component that relies on an object with data that can be dynamically added to the object, so for example, in a seperate .js file, I am exporting the following object:
export default {
defaultSection1: {
displayName: 'Action',
},
defaultSection2: {
displayName: 'Thriller',
},
}
I import this object in my component.
I got a debouncer setup from Lodash so that when data changes, it only fires the watcher once after two seconds. The watcher IS triggered perfectly fine for the data that is already in the object (type in the input text box in my example, and the watcher is triggered). But when I dynamically add data to the object, the watcher is not triggered at all. Only when I change routes back and forth, the data is updated, but the watcher is not triggered. Why is this? What can I do so that the watcher is triggered when data is dynamically being added to an object.
methods: {
fetchAndUpdateData(){
console.log('Fetching data...')
},
addCustomSection(){
//The watcher should be triggered when this function is called
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}
}
},
computed: {
dataWatcher() {
return this.data;
},
updateData() {
return debounce(this.fetchAndUpdateData, 2000);
},
},
watch: {
dataWatcher: {
handler() {
console.log('Watcher triggered')
this.updateData();
},
deep: true,
},
},
Why is the watcher not triggered, when clearly the data is being changed?
Another thing I noticed that is quite strange, is that in Vue 3.0, the watcher IS triggered with exactly the same code.
Codesandbox in Vue 2.6.11, watcher not triggering.
Codesandbox in Vue 3.0, watcher IS triggered with exactly the same code.
In vue 2 there's reactivity issue when updating an item in an array or a nested field in an object, to solve this you've to use this.$set() method :
this.$set(this.data,newSectionId, {displayName: 'Custom'})
this issue is solved in Vue 3. and you could just do :
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}

How to access firebase prop's values inside a method (Vuefire)?

In my component, I am doing :
firebase() {
const userId = firebase.auth().currentUser.uid
return {
race: userRef.child(userId).child('races').child(this.raceKey)
}
},
mounted () {
console.log(this.$firebaseRefs.race.name)
}
I can access the race property's values inside my component's template, but I cannot figure out how to access them inside a created hook or a method. The value is always undefined. How can I do this?
The structure for the race is:
race: {
name: "the name",
.....
}
This is because queries to Firebase Real Time Database are asynchronous, and therefore there is no guarantee that you get the result of your query in a lifecycle hook like mounted. In other words, the Firebase binding of your race object does not finish before the instance is mounted.
See the following posts for more detail and possible workaround with readyCallback:
https://github.com/vuejs/vuefire/issues/70 and https://github.com/vuejs/vuefire/issues/69
Solved it using the readyCallBack function. The problem was mounted triggered before the firebase reference had actually loaded. Thank you Renaud Tarnec for the help.
firebase() {
return {
race: {
source: userRef.child(firebase.auth().currentUser.uid).child('races').child("-LMgzo_50TzlfJwCblDS"),
asObject: true,
cancelCallback: function () {},
readyCallback: function() {
console.log("Firebase race was loaded")
this.renderMap()
}
}
}
}

Object's data missing when passing to Meteor.call

In my React/Meteor application, I am trying to pass an object with data from the state to a method on the server, for insertion into the database. However, there seems to be an issue passing the object from the React component to the Meteor method - one of the child objects ends up in the Meteor method, but all of its children are gone. I do nothing to the object except use check() to ensure it is an Object:
'Appointments.saveData'(dataObj) {
check(dataObj, Object);
console.log(dataObj);
// ....
}
Here's what happens on the front-end:
Meteor.call('Appointments.saveData', {
vitalsData: this.state.vitalsData || {},
subjectiveData: this.state.subjectiveData || '',
physicalExamData: this.state.physicalExamData || {},
rosData: this.state.rosData || {},
impressionData: this.state.impressionData || [],
extraNotes: this.state.extraNotes || ''
}, (err, res) => {
if (res && !err) {
this.refs.toasts.success(
'Data for this encounter has been saved.',
'Records saved!'
);
} else {
this.refs.toasts.error(
'An unknown error has occurred. Reload the page and try again.',
'Error!'
);
}
});
I combine all of my state variables into an object using {}, which in turn becomes dataObj in the method. However, dataObj.impressionData exists, and is an array containing objects, however, data is missing from any of the objects in the array.
For example, dataObj.impressionData[0].diagnosis should be an object, in fact, it is supposed to be an exact copy of an object already pulled from the database. However, if I console.log it, the object is empty.
I have verified that the data exists as it should at each step before passing to the Meteor method. I console.log the object immediately before calling Meteor.call and immediately after calling check in my method. I cannot for the life of me understand why data is missing.
What am I forgetting?
EDIT: I've changed my code so that the data is now added to the state directly from a ref. Now the server method does properly receive the object. However, in the following code:
if (dataObj.impressionData && dataObj.impressionData.length > 0) {
dataObj.impressionData.forEach(obj => {
console.log(obj); // obj.diagnosis exists and is as expected
const x = ICD10Codes.findOne({ _id: obj.diagnosis._id });
console.log(x); // this also works as it should
impressionFields.push({ patientId: appt.patient._id, diagnosis: x, note: obj.note, x });
});
}
Setting diagnosis to x, which I KNOW is a valid copy of the object straight from the database yields the same results:
meteor:PRIMARY> db.EncounterData.findOne()
...
"impression" : {
"patientId" : "47de32b428d8c4aaac284af3",
"appointmentId" : "TwL7DF9FoXPRgmrjR",
"fields" : [
{
"patientId" : "47de32b428d8c4aaac284af3",
"diagnosis" : {
}
}
]
},
...
I think I'm going crazy.
So your issue comes down to the fact that this.setState is an asynchronous function, so when you make your Meteor call, this.state hasn't actually be updated yet. As such, you need to wait for the this.setState call to finish. The only way to do this is to use the React lifecycle methods. You can use either componentWillUpdate (called before the next render) or componentDidUpdate (called after the next render).
var MyComponent = React.createClass({
save: function() {
...
case 'impression':
this.setState({ impressionData: data }, this.callServerMetho‌​d);
break;
...
},
// This is one of the React lifecycle methods
componentWillUpdate: function(nextProps, nextState) {
// Put your Meteor call here
// Make sure to use nextState instead of this.state
// This way you know that this.state has finished updating
}
});
I solved the issue myself - it turns out that I was importing my SimpleSchema objects as default but exporting my SimpleSchema objects as named. The SimpleSchema objects were thus invalid.

Meteor collection always returns undefined on client

Im new to meteor and have been trying to learn the framework via the discover meteor book. Im having a few issue understanding what exactly is going on in my application (found here https://github.com/Themitchell/Sound-wav.es).
Essentially, my understanding is that on my server side I allow publications for certain collections which take arguments from my client side subscribe calls. For this part on my server i have this in my server/publications.js file:
Meteor.publish('studios', function (options) {
return Studios.find({
userId: this.userId
}, options);
});
Meteor.publish('studio', function (id) {
return id && Studios.find({
userId: this.userId,
_id: id
});
});
Next we would need a controller to handle the routing and deal with waiting for any subscriptions required, then, once the subscriptions are ready (hence the waitOn) go and render the template providing the data function as a reactive data source for the templates.
So I then set up my 2 routes for indexing studios and showing one studio using iron router and 'Controllers' as follows:
StudiosIndexController = RouteController.extend({
template: 'studiosIndex',
increment: 20,
limit: function () {
return parseInt(this.params.studiosLimit) || this.increment;
},
findOptions: function () {
return {
sort: this.sort,
limit: this.limit()
};
},
waitOn: function () {
return Meteor.subscribe('studios', this.findOptions());
},
studios: function () {
return Studios.find({}, this.findOptions());
},
data: function () {
return {
studios: this.studios()
};
}
});
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
studio: function () {
return Studios.findOne(this.params._id);
},
data: function () {
return {
studio: this.studio()
};
}
});
Router.map(function () {
this.route('studiosIndex', {
path: '/',
controller: StudiosIndexController
});
this.route('showStudio', {
path: '/studios/:_id',
controller: ShowStudioController
});
});
Now this is great and works fine when displaying my index page. I get a list of collections which is reactive and the as i introduce new studios to the collection i see them get created on the server and on the client respectively. However in my show view when the view is rendered the data always seems to be empty. On dropping into my show controller's data function with a debugger I tried querying the Studios and this always returns undefined even when i try to fetch everything possible. Oddly I know that my publications are logging the correct id for a studio (using console.log). It seems that i get all the correct data up until the routing on the client side. The parameter ids are all correct but a find() call on studios always returns nothing. Am I missing something obvious here.
Its also worth noting i deleted my helpers for 'studios' and 'studio' in views/studios/index.js and views/studios/show.js respectively as my understanding is that this is what im doing with studios and studio in the controller. These helpers are now defined at the controller level and passsed to the reactive 'data' function. IS this correct?
TL;DR
With the code above my index action works however my show action fails where the data function always returns undefined and in fact on the show page i cannot get access to any of my studio information (Studios.find({}).count() always returns 0). I'm unsure how the 2 routes differ. Can anyone spot the issue?
Edit: Its also worth noting having looked through some of the iron router issues on github i have tried using this.ready(). The first time the route is run i hit data and this is false but then even wrapping my data helpers to wait for this.ready() gets an undefined return value when this.ready() returns true.
Extra Notes
Im running meteor 0.8.0 with latest 0.7.0 release of iron router and collection2 with simple schema, just in case you are interested / require this info.
EDIT: Possible solution
So having fiddled around it seems like my helpers are the issue. By using the 'studio' section and then calling this.studio() inside my data function this seems to break. If I change the code below:
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
studio: function () {
return Studios.findOne(this.params._id);
},
data: function () {
return {
studio: this.studio()
};
}
});
to this
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
data: function () {
return Studios.findOne(this.params._id);
}
});
My view renders correctly again. Im unsure where i saw this pattern however I had assumed the function assigned to 'studio' was essentially the same as doing
Template.showStudio.helpers({
studio: function () {
return Studios.findOne(this.params._id)
}
});
but it seems not!

Categories