Using React + Firebase, how to modify state inside a nested query? - javascript

I currently initialize my state like this:
getInitialState: function () {
return {
conversations: {},
messages: {},
availableConversations: {}
}
},
If I do something like:
convosRef.on('value', snapshot => {
this.setState({ conversations: snapshot.val() });
});
It works as desired... however:
users: {
uid: {
conversations: {
conversationid: true,
conversationid2: true
}
}
}
I successfully get the desired object:
userSnapshot.child('conversations').forEach(function (conversationKey) {
var conversationRef = database.ref('conversations').child(conversationKey.key);
conversationRef.on('value', function (conversationsSnapshot) {
var conversation = conversationsSnapshot.val();
conversationLoadedCount = conversationLoadedCount + 1;
myObject[conversationKey.key] = conversation;
// console.log(conversationKey.key);
//this.state.conversations = conversation;
if (conversationLoadedCount === conversationCount) {
console.log("We've loaded all conversations");
}
});
this.setState({ conversations: myObject });
});
});
However. I receive two errors and I can't affect the state:
First error:
`FIREBASE WARNING: Exception was thrown by user callback. TypeError: Cannot read property 'setState' of null``
And slightly similar:
Uncaught TypeError: Cannot read property 'setState' of null
I based my code on this excellent answer with no success whatsoever:
Firebase two-way relationships / Retrieving data
This is running inside the componentDidMount function.
Any suggestions on how to affect the state?

In ES6 if you use the fat arrow function this would be much cleaner and "this" would be bound, For example:
userSnapshot.child('conversations').forEach((conversationKey) => {
var conversationRef = database.ref('conversations').child(conversationKey.key);
conversationRef.on('value', (conversationsSnapshot) => {

I found the solution to my problem just by trying to bind it.
The solution is to use bind(this) whenever I do a call on the firebase.database().ref().
Here is the final snippet, hope it helps a poor soul like me and if someone has a better answer or some explanation on how to improve this please let me know:
var myObject = {};
usersRef.on('value', function (userSnapshot) {
// First we get the qty of conversations.
var conversationCount = userSnapshot.child('conversations').numChildren();
// we initialize an empty counter
var conversationLoadedCount = 0;
// we traverse now the conversations ref with the past conversations.
userSnapshot.child('conversations').forEach(function (conversationKey) {
var conversationRef = database.ref('conversations').child(conversationKey.key);
conversationRef.on('value', function (conversationsSnapshot) {
var conversation = conversationsSnapshot.val();
conversationLoadedCount = conversationLoadedCount + 1;
myObject[conversationKey.key] = conversation;
// console.log(conversationKey.key);
this.state.conversations[conversationKey.key] = conversation;
this.setState({ conversations: this.state.conversations });
if (conversationLoadedCount === conversationCount) {
console.log("We've loaded all conversations");
}
}.bind(this)); // one for the usersRef
}.bind(this)); // another one for the forEach inside
}.bind(this)); // the final one for the conversationRef.on...

Related

Get global module variable in vanilla javascript

So i have below module created according to an example i found.
I initialize it with
equipment_booking = new equipment({
equipment: '#equipment_order',
type: '#equipment_type_order',
validation: '#equipment_validation_order',
submit: "#submit_booking"
});
What I want to do is access the typeVar from outside the module. I found out that I need to use the this. in front of it. And indeed I could access it. However I can't seem to access it inside the module functions. How would I accomplish this? This is a part of the module I have written. I have tried different ways in the checkInput function. Any help or suggestions are realy appriciated.
var equipment = (function() {
function equipment(options) {
// Set the standard options
var o = {
equipment: null,
type: null,
validation: null,
submit: null
};
// Get some other needed variables
number = "";
this.typeVar = 0;
var error = [{
code: 0,
msg: ""
}];
// Then we can override the given options to the standard options
for (var k in options) {
if (options.hasOwnProperty(k)) {
o[k] = document.querySelector(options[k]);
}
}
// Add the eventlisteners
o.equipment.addEventListener('keydown', checkInput);
o.equipment.addEventListener('blur', validateInput);
o.type.addEventListener('change', validateInput);
function checkInput(e) {
console.log("CheckInput called"); // This console.log is called
console.log(typeVar); // Error: typeVar is not defined
console.log(this.typeVar); // Undefined
e.preventDefault(); // The e works
}
function validateInput() {
// Validating input
}
}
return equipment;
})();
It seems that you are using some very old vanilla js. I suppose it is to support old browsers. Let's keep that support using a simple technique that it's called "saving this in a helper variable self"
function equipment(options) {
// Set the standard options
var o = {
equipment: null,
type: null,
validation: null,
submit: null
};
// Get some other needed variables
number = "";
this.typeVar = 0;
var error = [{
code: 0,
msg: ""
}];
// Then we can override the given options to the standard options
for (var k in options) {
if (options.hasOwnProperty(k)) {
o[k] = document.querySelector(options[k]);
}
}
// Add the eventlisteners
o.equipment.addEventListener('keydown', checkInput);
o.equipment.addEventListener('blur', validateInput);
o.type.addEventListener('change', validateInput);
var self = this;
function checkInput(e) {
console.log("CheckInput called"); // This console.log is called
console.log(typeVar); // Error: typeVar is not defined
console.log(this.typeVar); // Undefined
console.log(self.typeVar); // Defined
e.preventDefault(); // The e works
}
function validateInput() {
// Validating input
}
}
return equipment;
})();
The reason this works is because when the interpreter creates a new function such as checkInput, it sees all the variables that are around and puts them inside a "backpack". This backpack of variables is available when you execute the function later. Now you may ask: "Why is this not referencing my equipment class? Why is not in the backpack?". Thats because this in that callback is referencing other thing from the event callback. It got "replaced" inside that callback. Thats why you don't have it.
Edit:
A more modern approach would be to use class syntax without changing too much the code, and for the callbacks, we would use class properties as arrow functions which hold the correct this class context when the event listeners call them.
Please, take a careful look at the name "Equipment", now it has a capital "E". The class instantiation should be new Equipment() now.
More info on https://devopedia.org/naming-conventions
class Equipment {
constructor(options) {
// Set the standard options
const o = {
equipment: null,
type: null,
validation: null,
submit: null
};
// Get some other needed variables
let number = "";
this.typeVar = 0;
const error = [{
code: 0,
msg: ""
}];
// Then we can override the given options to the standard options
for (let k in options) {
if (options.hasOwnProperty(k)) {
o[k] = document.querySelector(options[k]);
}
}
// Add the event-listeners
o.equipment.addEventListener('keydown', this.checkInput);
o.equipment.addEventListener('blur', this.validateInput);
o.type.addEventListener('change', this.validateInput);
}
checkInput = (e) => {
console.log("CheckInput called"); // This console.log is called
console.log(this.typeVar); // defined
e.preventDefault(); // The e works
}
validateInput = () => {
// Validating input
}
}

Can not read property of array element in javascript

Im fairly new to javascript and im trying to find an object of an array i created by the configID property. I used the method find() for this.
JS Code:
var configurationArray = flow.get("configurationArray") || [];
var configurationId = msg.topic.split("/")[1];
var configuration = {
configID: this.configurationID,
configurationModules: this.MSGesture.payload
};
if(!configurationArray.find(x => x.configID == this, configurationId)){
configurationArray.push(this.configuration);
} else {
//to do
}
I am using node-red which gives me flow and msg.
The Error i get:
Cannot read property 'configId' of undefined
Any help is appreciated
You could destructure the property and add a default object.
Then take some instead of find, because you need only the check.
At last, omit this and take directly the value.
if (!configurationArray.some(({ configID } = {}) => configID === configurationId)) {
configurationArray.push(this.configuration);
} else {
//to do
}
If you like to have an abstract callback, you could take a closure over configurationId, like
const hasId = id => ({ configID } = {}) => configID === id;
if (!configurationArray.some(hasId(configurationId)) {
configurationArray.push(this.configuration);
} else {
//to do
}

module.export and global objects

I'm confused.
Occasionally when my web api receives data it mixes the data up between objects and it appears to me that the global object in the file is actually being persistent..
Here is the basic layout of the code
handlers.js
const something = require('./otherfile')
let handlers ={}
handlers.customers = function (data, callback) {
let acceptableMethods = ['post'];
if (acceptableMethods.indexOf(data.method) > -1) {
handlers._customers[data.method](data, callback);
} else {
callback(405);
}
};
handlers._customers = {}
handlers._customers.post = async function (data, callback) {
customer.new(data);
callback(200, 'success')
}
otherfile.js
let contacts_list = [];
let accountData = {};
module.exports = something = {
new: {
dostuff: async function (data) {
// update and reference global objects here..
accountData.name = data.name;
accountData.otherProperty = await somefunction(data.prop)
}
}
}
I expected that since it is requiring an exported module that each time it would call the exported module it would be treated as its own object, however, it seems that the object is not being treated as unique and is instead being overwritten in part and 'randomly'. This suggests to me that I may be able to export a mutating object such as an array across files
Am I correct in that the global is being persisted across multiple requests?
Would setting the global within the export object affect the behaviour of this object in any way? In this case I don't want this data to mutate.
Thanks in advance for your constructive criticisms and guidance :)
[Restructure your code so you are creating a new object on every request. Module's are cached on the first require so all of your variables and object properties will be persisted across calls.
// handler.js
const somethingFactory = require('./otherfile')
module.exports = function(){
let handlers = {}
const something = somethingFactory();
handlers.customers = function (data, callback) {
let acceptableMethods = ['post'];
if (acceptableMethods.indexOf(data.method) > -1) {
handlers._customers[data.method](data, callback);
} else {
callback(405);
}
};
handlers._customers = {}
handlers._customers.post = async function (data, callback) {
customer.new(data);
callback(200, 'success')
}
return handlers;
};
otherfile.js
module.exports = function(){
let contacts_list = [];
let accountData = {};
return {
new: {
dostuff: async function (data) {
// update and reference global objects here..
accountData.name = data.name;
accountData.otherProperty = await somefunction(data.prop)
}
}
}
};

React-Native — Accessing functions outside componentDidMount

I am facing a scope issue when calling a function inside componentDidMount. The function getArrival is defined outside componentDidMount. I tried a few things with no success.
What is the correct way to define getArrival so that I can access it inside the componentDidMount?
Thank you in advance.
Code:
getArrival = (lines) => {
return fetch('https://api.tfl.gov.uk/Line/' + lines + '/Arrivals/' + nearestStopId)
.then((response) => response.json())
.then((arrivalData) => {
}, function() {
// do something with new state
});
console.log(arrivalData);
})
.catch((error) => {
console.error(error);
}
);
}
componentDidMount() {
// Variable to store user data
let userLines = null
// Variable to store nearest stop id
let nearestStopId = null
// Check all variables against the API
for (i = 0; i < apidata.length; i++) {
// Checks user stops id against beacon id
if (userdata[i].bustopId == beaconId) {
// Store match into variable
nearestStopId = userdata[i].bustopId
userLines = userdata[i].buses
// matchStop(nearestStopId)
break // Breaks the loop
}
console.log(userLines)
}
// Data for stops
return fetch('https://api.tfl.gov.uk/StopPoint/' + nearestStopId )
.then((response) => response.json())
.then((stopData) => {
let linesId = []
let selectedLines = []
console.log("linesId at this stop", stopData.lines)
console.log("userlines", userLines)
// Loop through the data in stopData
for (i = 0; i < stopData.lines.length; i++) {
// ... and add all buses id from the API to the array
let currentLine = stopData.lines[i].id
linesId.push(currentLine)
// Checks if user lines are included in current lines ...
if ( userLines.includes(currentLine) ) {
// ... if so, push it into selected lines
selectedLines.push(currentLine)
getArrival(lines) // This gets an error not defined
let lines = selectedLines.toString()
}
}
console.log("selectedLines", selectedLines, "from ", linesId)
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(selectedLines),
}, function() {
// do something with new state
});
})
.catch((error) => {
console.error(error);
}
);
}
I'm going to assume that the code you've shown creating getArrival is inside your class body, e.g.: (you've confirmed that now)
class YourComponent extends React.Component {
getArrival = () => {
// ...
};
componentDidMount() {
// ...
}
// ...
}
If so, that's using the class properties proposal (not yet standard, but at Stage 3) and it creates a property on your instance (like this.getArrival = ...; in your constructor).
So you would access it on this:
this.getArrival(lines)
// ^^^^^
That code is in a callback (or two), but they all appear to be arrow functions, and so they close over this and will use the one componentDidMount was called with (which is the right one).
See this question's answers for why I checked that the callbacks were arrow functions, and what to do in cases where you can't use arrow functions for whatever reason.

Uncaught TypeError: Cannot read property 'isSynchronized' of undefined

I have a problem with sorting my ExtJS grid. I'm using Ext JS version 5.1.1.451.
The grid loads well but when I try to sort the columns I get the "Uncaught TypeError: Cannot read property 'isSynchronized' of undefined" error.
The method addCls is triggered on mouse movement and when the error occures, me.getData() returns undefined, where me is
constructor
$observableInitialized: true
component: constructor
config: Object
destroy: () {}
dom: null
el: null
events: Object
hasListeners: statics.prepareClass.HasListeners
id: "ext-quicktips-tip"
initConfig: () {}
initialConfig: Object
isDestroyed: true
lastBox: null
managedListeners: Array[0]
shadow: null
This is the addCls method where the error occurs:
addCls: function(names, prefix, suffix) {
var me = this,
elementData = me.getData(),
hasNewCls, dom, map, classList, i, ln, name;
if (!names) {
return me;
}
if (!elementData.isSynchronized) {
me.synchronize();
}
Has anyone encounter this problem before? Is it a known issue or am I doing something wrong?
Thank you
I've overriden the handleMouseDown method and commented out the quick tips part.
Ext.override(Ext.dd.DragDropManager, {
handleMouseDown: function (e, oDD) {
var me = this,
xy, el;
me.isMouseDown = true;
//if (Ext.quickTipsActive) {
// Ext.tip.QuickTipManager.ddDisable();
//}
me.currentPoint = e.getPoint();
if (me.dragCurrent) {
me.handleMouseUp(e);
}
me.mousedownEvent = e;
me.currentTarget = e.getTarget();
me.dragCurrent = oDD;
el = oDD.getEl();
Ext.fly(el).setCapture();
xy = e.getXY();
me.startX = xy[0];
me.startY = xy[1];
me.offsetX = me.offsetY = 0;
me.deltaX = me.startX - el.offsetLeft;
me.deltaY = me.startY - el.offsetTop;
me.dragThreshMet = false;
}
});
If anyone has a better answer, post it and I will mark it if it's good.
I was having this issue because I was rendering a different value based on some mappings I needed to load separately. If the mappings weren't loaded into the viewmodel then the renderer seemed to fail with that error.
I solved it with some checks for existence of what it needed:
// on the renderer for the column where I have a combobox editor field
renderer: function(value) {
var mappings = vm.get('ruleMapping')
if(!!mappings) {
var rule = _.find(mappings, ['id', value]
if(!!rule) {
return `<a href="${rule.link}>${rule.name}</a>"`
}
}
return 'Setting value'
}

Categories

Resources