(Small edit: "ce" is a shortcut for "React.createElement")
I have been working on a React/WebSockets/AJAX project for a chat room/message board. I am quite new to React, and I have caught on to most of it but I am having trouble with dynamically updating a list/refreshing its items.
What I want to do is every time my WebSocket receives an "update" message, I want to update the lists with the latest messages. The issue I am having is that they are not displaying anything, even though my update method is being called properly. I am getting no errors.
In my UserPageComponent, I have:
constructor(props) {
super(props);
this.state = {
channelType: "global",
messageToSend: "",
target: "",
globalMessages: [],
privateMessages: []
};
}
In my UserPageComponent render I have this:
... return 'Global Chat: ',
ce('ul', {id: "globalMessageDiv"}, this.state.globalMessages),
'Private Chat: ',
ce('ul', {id: "privateMessageDiv"}, this.state.privateMessages),
...
Here is my update (called every time a new message is sent - keep in mind globalMsgs/privateMsgs is populated properly with ALL messages sent as of when it was called).
updateData() {
const globalMsgs = this.getMessages("global");
const privateMsgs = this.getMessages("private");
var compiledGms = [];
var compiledPms = [];
globalMsgs.map((gm) => {
var gmToLi = ce('li', gm);
compiledGms.push(gmToLi);
});
privateMsgs.map((pm) => {
var pmToLi = ce('li', pm);
compiledPms.push(pmToLi);
});
this.setState({globalMessages: compiledGms});
this.setState({privateMessages: compiledPms});
}
The update function is called whenever I send a message and works like needed. (example below)
I'm unsure what else I can provide, however here is an example of what "globalMsgs" holds: data in globalMsgs/privateMsgs variables example
Try this below code
updateData() {
const globalMsgs = this.getMessages("global");
const privateMsgs = this.getMessages("private");
var compiledGms = [];
var compiledPms = [];
for(var i=0;i<globalMsgs.length;i++){
var gmToLi = ce('li', globalMsgs[i]);
compiledGms.push(gmToLi);
if(i==globalMsgs.length-1){
this.setState({globalMessages: compiledGms});
}
}
for(var i=0;i<privateMsgs.length;i++){
var pmToLi = ce('li', privateMsgs[i]);
compiledPms.push(pmToLi);
if(i==privateMsgs.length-1){
this.setState({privateMessages: compiledPms});
}
}
}
Related
Creating my first ReactJS Website and using Node in the back-end, currently in the code that follows I fetch data that I then print on the page. I manage to print the names of the people in a project, their picture and their email from the server BUT the description of the project i get the error :
TypeError: Cannot read property '0' of undefined
Which I do not understand.
Here is the code :
class ProjectPage extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
description: [],
mail: [],
name: [],
};
this.getNames = this.getNames.bind(this);
this.getPictures = this.getPictures.bind(this);
this.getMails = this.getMails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
I create the class and all the elements that are required
componentDidMount() {
console.log("BEGIN componentDidMount");
this.fetchDetails();
this.fetchNames();
this.fetchMails();
console.log("END componentDidMount");
}
Call all the function in my componentDidMount()
fetchDetails() {
console.log("BEGIN fetchDetails()");
let url = 'http://192.168.1.33:8080/getprojectdetails/Aprite';
console.log("url details = " + url);
fetch(url)
.then(results => {
var json = results.json();
return json;
})
.then(data => {
this.setState({ description: data });
})
console.log("END fetchData()");
}
Here is the fetch of the project description
getDetails = () => {
let lines = [];
let nbr = this.state.description.length;
console.log("nbr = " + nbr);
if (nbr){
console.log("lines = " + this.state.description[0].P_Description);
for (let i = 0; i < nbr; i++)
lines.push(<div key={this.state.descripton[i].P_Description}></div>);
}
return (lines);
}
And the function to print the data in the Render() function
But when i try to print this data, the value of nbr passes from 0 to 1 then to 0 again... in the console log I can see the description but it doesn't appear on the website and I don't get it.
Please help me ?
There is a typo in the inner loop inside the getDetails function
You should write this.state.description not this.state.descripton
Hope this solves your problem :)
So with the React render lifecycle system, the componentDidMount will actually happen after the first render. During that first render, you're trying to access the first element of an empty array, which is the error you are seeing.
In order to solve this problem, in your render method, you should have a fallback something to render while we wait for the fetchDetails to return a value from the server. If you want it to render nothing, you can just return null.
ie.
const { description = [] } = this.state;
if (description.length === 0) {
return null;
}
return this.getDetails();
As a side note, in order to avoid having all of those (which gets pretty unmaintainable):
this.getNames = this.getNames.bind(this);
this.getPictures = this.getPictures.bind(this);
this.getMails = this.getMails.bind(this);
this.getDetails = this.getDetails.bind(this);
You can just define them as class properties like so:
getNames = () => {
// do stuff
}
I'm using handsontable on Meteor 1.4.1 through the plugin awsp:handsontable#0.16.1
The problem I have is that the matrix gets re-rendered every time I change a value, which creates two issues. The first is that the focus of the edited cell gets lost and the scroll goes back to the top. The second is that sometimes the values are not saved because the data of the matrix get reloaded with each change.
The way I'm subscribing to the data and rendering the table is as follows:
Template.connectivityMatrix.onCreated(function () {
this.activeScenario = () => Session.get('active_scenario');
this.autorun(() => {
this.subscribe('connectivityMatrixUser', this.activeScenario());
});
});
Template.connectivityMatrix.onRendered(function () {
this.autorun(() => {
if (this.subscriptionsReady()) {
const activeScenario = Session.get('active_scenario');
const currentScenario = Scenarios.findOne({_id: activeScenario});
const currentTurn = currentScenario.turn;
const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
var myData = []; // Need this to create instance
var container = document.getElementById('connectivity-matrix');
var hot = new Handsontable(container, { // Create Handsontable instance
data: myData,
startRows: numObj,
startCols: numObj,
afterChange: function (change, source) { // 'change' is an array of arrays.
if (source !== 'loadData') { // Don't need to run this when data is loaded
for (i = 0; i < change.length; i++) { // For each change, get the change info and update the record
var rowNum = change[i][0]; // Which row it appears on Handsontable
var row = myData[rowNum]; // Now we have the whole row of data, including _id
var key = change[i][1]; // Handsontable docs calls this 'prop'
var oldVal = change[i][2];
var newVal = change[i][3];
var setModifier = {$set: {}}; // Need to build $set object
setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
ConnectivityMatrix.update(row._id, setModifier);
}
}
}
});
myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch(); // Tie in our data
hot.loadData(myData);
}
});
});
What I want to achieve is to create the matrix only once instead of recreate it with each data change so the focus stays and the data gets always saved.
So I've tried leaving only the last two lines inside the block of this.autorun() as suggested in this question
Template.connectivityMatrix.onRendered(function () {
const activeScenario = Session.get('active_scenario');
const currentScenario = Scenarios.findOne({_id: activeScenario});
const currentTurn = currentScenario.turn;
const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
var hot = new Handsontable(container, { // Create Handsontable instance
...
});
this.autorun(() => {
if (this.subscriptionsReady()) {
myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch(); // Tie in our data
hot.loadData(myData);
}
});
});
but then the first time I load the page, the data is not available so I get the error
Cannot read property 'turn' of undefined
Therefore, how can I properly get all the data needed to create the table without re-rendering it when a cell value changes?
Thanks in advance for any help.
You are trying to query the Scenarios and ConnectivityMatrix collections before they are ready. Move all mongo queries inside your this.subscriptionsReady() conditional block.
The way I managed to do what I needed is by stopping the computation after the matrix gets rendered. The code is as follows:
Template.connectivityMatrix.onRendered(function () {
this.autorun((computation) => {
if (this.subscriptionsReady()) {
const currentScenario = Scenarios.findOne({_id: activeScenario});
const currentTurn = currentScenario.turn;
const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
var myData = []; // Need this to create instance
var container = document.getElementById('connectivity-matrix');
var hot = new Handsontable(container, { // Create Handsontable instance
data: myData,
colHeaders: arrayRowsCols,
rowHeaders: arrayRowsCols,
height: '450',
maxRows: numObj,
maxCols: numObj,
columns: columns,
afterChange: function (change, source) { // 'change' is an array of arrays.
if (source !== 'loadData') { // Don't need to run this when data is loaded
for (i = 0; i < change.length; i++) { // For each change, get the change info and update the record
var rowNum = change[i][0]; // Which row it appears on Handsontable
var row = myData[rowNum]; // Now we have the whole row of data, including _id
var key = change[i][1]; // Handsontable docs calls this 'prop'
var oldVal = change[i][2];
var newVal = change[i][3];
var setModifier = {$set: {}}; // Need to build $set object
setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
ConnectivityMatrix.update(row._id, setModifier);
}
}
}
});
myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch(); // Tie in our data
hot.loadData(myData);
computation.stop();
}
});
});
I am learning about Node and Feathers on a job. Need to make a simple app that would use feathers to load the [nedb] with sample data.
var fake = require('./fake.js');
var feathers = require('feathers-client');
var io = require('socket.io-client');
var socket = io("http://127.0.0.1:8000");
var app = feathers()
.configure(feathers.socketio(socket));
var accountsAPIService = app.service('/api/accounts');
var dummyData = fake();
// import dummy data
for ( var i = 0; i < dummyData.accounts.length; i++) {
// console.log(dummyData.accounts[i]);
var params = { query: {}};
accountsAPIService.create(dummyData.accounts[i], params).then(function(account) {
console.log("inserted: ", account);
});
}
// read back inserted records
accountsAPIService.find(params, function(accounts) {
console.log("accounts: ", accounts);
});
i just need to insert items from the array dummyData.accounts into the server.
When I run the script, it seems that nothing is being imported.
When I read the records back, it returns:
accounts: null
What is the proper way of inserting/creating records with Feathers?
Could not figure out how to use ".then" so used a regular form:
for ( var i = 0; i < dummyData.accounts.length; i++) {
var params = { query: {}};
accountsAPIService.create(dummyData.accounts[i], params, function(error, account) {
// console.log("inserted: ", account);
});
}
That works fine.
To read the data back, I corrected the method signature. Then, it works. :)
accountsAPIService.find(function(error, accounts) {
console.log("accounts: ", accounts);
});
What I am trying to do is get the Game ID that is created by the APIService.postData for the game. I need to then take that Game ID and put it into the Angular foreach loops so that on the RESTful side, the foreign key constraints hold true.
How can I get that game ID out of there?
P.S. I am well aware of the scope issue
this.createGame = function() {
APIService.postData('game', '', $scope.newGameData).then(function (data) {
$scope.newGameID = data.id;
});
// Looping through each added class and adding the game_id onto the object in
// order for the DB insertion to go smoothly on the RESTful side.
angular.forEach($scope.newGameData.classes, function (key, value) {
$scope.newGameData.classes[value].game_id = $scope.newGameID;
APIService.postData('game-class', '', $scope.newGameData.classes[value]);
});
// Looping through each added race and pushing the game_id onto the object in
// order for the DB insertion to go smoothly on the RESTful side.
angular.forEach($scope.newGameData.races, function (key, value) {
$scope.newGameData.races[value].game_id = $scope.newGameID;
APIService.postData('game-race', '', $scope.newGameData.races[value]);
});
$scope.newGameData = {
name: ""
};
$scope.race_counter = 0;
$scope.class_counter = 0;
$scope.newGameData.classes = [{id: $scope.class_counter}];
$scope.newGameData.races = [{id: $scope.race_counter}];
$scope.successMessage = "New 'Game' has been added!";
$scope.action = 'showGames'; // Default action.
this.getGames();
$window.scrollTo(0, 0);
};
Figured it out. The foreach loops needed to go into the 'then' callback. The GameID kept coming up undefined because the POST request hadn't actually finished yet. On top of that, the $scope.newGameData was getting screwed up, so I assigned both arrays to their own local variable and it works great.
this.createGame = function() {
var newGameID = '';
var classData = $scope.newGameData.classes;
var raceData = $scope.newGameData.races;
APIService.postData('game', '', $scope.newGameData).then(function (data) {
newGameID = data.id;
// Looping through each added class and adding the game_id onto the object in
// order for the DB insertion to go smoothly on the RESTful side.
angular.forEach(classData, function (key, value) {
classData[value].game_id = newGameID;
APIService.postData('game-class', '', classData[value]);
});
// Looping through each added race and pushing the game_id onto the object in
// order for the DB insertion to go smoothly on the RESTful side.
angular.forEach($scope.newGameData.races, function (key, value) {
raceData[value].game_id = newGameID;
APIService.postData('game-race', '', raceData[value]);
});
});
$scope.newGameData = {
name: ""
};
$scope.race_counter = 0;
$scope.class_counter = 0;
$scope.newGameData.classes = [{id: $scope.class_counter}];
$scope.newGameData.races = [{id: $scope.race_counter}];
$scope.successMessage = "New 'Game' has been added!";
$scope.action = 'showGames'; // Default action.
this.getGames();
$window.scrollTo(0, 0);
};
I have three entities: selected_spo, selected_product, selected_stock.
I need to update a FK in the existing entity selected_stock in order to link it to the newly created entity new_spoxs.
new_spoxs has mandatory FKs with entities selected_spo and selected_product
If I call myapp.applyChanges() immediately after I do the assignments then the whole update process enters in a very strange state.
The newly created entity new_spoxs is saved to DB but the FK selected_stock.supplier_po_x_stocks is not created.
There is no error in the [Function onError] of the Promise myapp.applyChanges().then... The UI keeps telling me that there are Unsaved Changes but Save changes button has no effect.
I also tried to solve this in more steps.
First create the new entity, do a myapp.applyChanges(), update the FK link selected_stock.supplier_po_x_stocks = new_spoxs; and then do another final myapp.applyChanges().
This was also NOT successful without that delay.
It ONLY works if I use a delay as you can see in the code !
This is totally not OK.
Where I am wrong ?
Or maybe this is a bug in LightSwitch ?!
var p_spo = myapp.activeDataWorkspace.storeData.supplier_poes_SingleOrDefault(id_spo_selected);
var p_product = myapp.activeDataWorkspace.storeData.products_SingleOrDefault(id_product_selected);
var p_stock = myapp.activeDataWorkspace.storeData.stocks_SingleOrDefault(id_stock_selected);
var promises_to_execute = [p_spo, p_product, p_stock];
var promises_executor = promises_to_execute.map(function (_p) {
return _p.execute();
});
WinJS.Promise.join(promises_executor).then(function (promises_result) {
var selected_spo = promises_result[0].results[0];
var selected_product = promises_result[1].results[0];
var selected_stock = promises_result[2].results[0];
//Next create a new supplier_po_x_stocks entity
//var new_spoxs = new myapp.supplier_po_x_stock();
var new_spoxs = myapp.activeDataWorkspace.storeData.supplier_po_x_stocks.addNew();
new_spoxs.product = selected_product;
new_spoxs.supplier_po = selected_spo;
new_spoxs.qty_requested = qty_to_be_ordered;
new_spoxs.qty_entered = 0;
//This is not working without WinJS.Promise.timeout(1000) ... !!! NOT OK !!!
selected_stock.supplier_po_x_stocks = new_spoxs;
WinJS.Promise.timeout(1000).then(function () {
myapp.applyChanges().then(
function () {
screen.v_spo_products_to_be_ordereds.refresh();
msls.showMessageBox('Save ok');
},
function (err) {
msls.showMessageBox(JSON.stringify(err, null, 4));
});
});
}, function (err) {
msls.showMessageBox(JSON.stringify(err, null, 4));
});