Update React state via Socket.io - websocket

My React component uses data from socket.io as it's state.
I am unsure how to just update the state when the data is updated without re-rendering the entire component.
Example code.
var socket = io();
var data = {
components: [{key: '',name: '',markup: ''}]
};
socket.on('data', function(_) {
data = _;
});
var Components = React.createClass({
displayName: "Components",
getInitialState: function getInitialState() {
return data;
},
handleChange: function handleChange() {
this.setState(data);
},
render: function render() {
/* render */
}
});
ReactDOM.render(
React.createElement(Components, {
data: data
}),
document.getElementById('main')
);

You can add socket event listener to componentDidMount, like this
var socket = io();
var data = {
components: [{key: '',name: '',markup: ''}]
};
var Components = React.createClass({
displayName: "Components",
componentDidMount: function () {
socket.on('data', this.handleData);
},
componentWillUnmount: function () {
socket.removeListener('data', this.handleData);
},
handleData: function (data) {
this.setState(data);
},
getInitialState: function () {
return data;
},
handleChange: function handleChange() {
this.setState(data);
},
render: function render() {}
});

Related

Async call for computed property - Vue.js

I have a computed property that will only be used if a match for a property exists. Because of this, I'm making the call to get the data asynchronous so that it's only retrieved when needed. I'm having an issue though trying to make an async call to return data for a computed property.
Below is what I have:
new Vue({
el: "#formCompleteContainer",
data: {
form: {},
components: []
},
computed: {
employeeList: function () {
var self = this;
if (_.some(this.components, function (component) {
return component.ComponentInfo.Type === 8
})) {
var employees = [];
$.ajax({
url: "/Form/GetAllUsers",
type: "GET"
}).done(function (results) {
employees = results;
});
return employees;
} else {
return [];
}
}
}
});
I know this isn't working because I'm returning before the call is complete. I've seen how to use deferredobjects and what not but I can't seem to figure out how to implement it with Vue.
For your use case, I don't think computed property can implement the goal.
My solution:
create one data property as one 'defered' object,
then uses one watch to async call your backend to get new data, finally assign to the defered object
like below demo:
Vue.config.productionTip = false
app = new Vue({
el: "#app",
data: {
product: "Boots",
deferedProduct: ''
},
watch: {
product: function (newVal, oldVal) {
setTimeout(() => {
this.deferedProduct = 'Cats in ' + newVal + '!'
}, 1500)
}
},
methods: {
nextProduct: function () {
this.product += 'a'
}
}
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<button #click="nextProduct()">Click Me!</button>
<h2>{{product}}</h2>
<h2>{{deferedProduct}}</h2>
</div>
This is what vue-async-computed is meant for. It resolves the promise you returned and handles any race conditions.
new Vue({
el: "#formCompleteContainer",
data: {
form: {},
components: []
},
asyncComputed: {
employeeList: function () {
if (_.some(this.components, function (component) {
return component.ComponentInfo.Type === 8
})) {
return $.ajax({
url: "/Form/GetAllUsers",
type: "GET"
});
} else {
return Promise.resolve([]);
}
}
}
});
After doing some more research I have gone another route. I agree with Sphinx that I don't think what I am trying to achieve will work with a computed property.
Instead, this is what I am going with:
new Vue({
el: "#formCompleteContainer",
data: {
form: {},
components: [],
employees: []
},
methods: {
getEmployees: function () {
var self = this;
if (_.some(this.components, function (component) {
return component.ComponentInfo.Type === 8;
})) {
$.ajax({
url: "/Form/Form/GetAllUsers",
type: "GET"
}).done(function (results) {
self.employees = results;
});
}
}
},
created: function () {
this.form = pageModel.Form;
this.components = pageModel.Components;
},
mounted: function () {
this.getEmployees();
}
});
As pointed out already, mounted and other 3rd party solutions can work.
However, better readability and component loading will come from putting the desired Promise within a data property. And then using the Vue lifecycle hook created, we can wait for that Promise to resolve with a .then.
For example:
requestService.js:
...
async foo(){
let myRequest = someRequest.createInstance()
await myRequest.onReady()
return myRequest.getSomePromise()
}
...
And then import the service into your component, as well as declaring a data prop:
myComponent.vue
...
data: (){
myPromiseLoc: null,
}
...
created: (){
requestService.foo().then( result =>
{
this.myPromiseLoc = result
}
}
...

Marker variable is undefined after get in Axios, VUE JS + LEAFLET + Axios

The Axios response.data is okay. But when I use the markers variable in rendering the markers is undefined. I am a newbie and badly need your help for our project.
I am trying to render the markers from the link described in the code, but some I placed the axios request in the created, and in the mounted is the rendering of the leaflet map.
Screenshot of the code
<script>
/** Script Vue JS **/
new Vue({
el: '#view-map',
data: {
map,
map_link:'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
markerOption: {
clickable: true,
draggable: false
},
mapOptions: {
center: [7.3087, 125.6841],
zoom:8
},
markers:[], //[{"image":"GMS-4-0112018-467_1527086274.jpg","derivation_code":"GMS-4-0112018-467","sample_description":"test 1 test is a test that test will be tested in test","latitude":"6.428152","longitude":"125.317857"},{"image":"GMS-1-0112018-963_1527134301.jpg","derivation_code":"GMS-1-0112018-963","sample_description":"nalaya lang","latitude":"7.311647","longitude":"125.636461"}],
selectedSample: [],
},
methods: {
getMarkers: function (){
axios.get('http://127.0.0.1:8000/marker').then(response => {
this.markers = response.data;
}).catch(error =>( console.log(error) ));
},
renderMarker: function(){
for ( i = 0; i < this.markers.length; i++){
console.log(this.markers[i]);
var marker = new L.Marker([this.markers[i].latitude, this.markers[i].longitude], this.markerOption);
marker.addTo(this.map);
marker.bindPopup(`
<h6 class="display-6">${this.markers[i].derivation_code}</h6>
<img src="storage/images/${this.markers[i].image}" style="height:100%;width:100%">
`);
}
},
markerClicked: function(mrkr_data){
this.selectedSample = mrkr_data.derivation_code;
console.log(this.selectedSample);
}
},
created: function(){
this.getMarkers();
},
mounted: function(){
this.map = new L.map('map', this.mapOptions);
this.map.addLayer(new L.TileLayer(this.map_link));
console.log(this.markers);
this.renderMarker();
}
});
</script>
I solved my problem. I treated the fetching of data in Axios get property as a Synchronous but it is an Asynchronous. Base in my previous code, I accessed the data when it is not updated so the value is blank.
/** Script Vue JS **/
new Vue({
el: '#view-map',
data: {
map,
map_link:'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
markerOption: {
clickable: true,
draggable: false
},
mapOptions: {
center: [7.3087, 125.6841],
zoom:8
},
markers:[], //[{"image":"GMS-4-0112018-467_1527086274.jpg","derivation_code":"GMS-4-0112018-467","sample_description":"test 1 test is a test that test will be tested in test","latitude":"6.428152","longitude":"125.317857"},{"image":"GMS-1-0112018-963_1527134301.jpg","derivation_code":"GMS-1-0112018-963","sample_description":"nalaya lang","latitude":"7.311647","longitude":"125.636461"}],
selectedSample: [],
},
created: function(){
this.getMarkers();
},
mounted: function(){
this.map = new L.map('map', this.mapOptions);
this.map.addLayer(new L.TileLayer(this.map_link));
},
watch: {
markers: function(){
this.renderMarker();
}
},
methods: {
getMarkers: function (){
axios.get('http://127.0.0.1:8000/marker').then(response => {
this.markers = response.data;
console.log(this.markers);
}).catch(error =>( console.log(error) ));
console.log(this.markers);
},
renderMarker: function(){
for ( i = 0; i < this.markers.length; i++){
console.log(this.markers[i]);
var marker = new L.Marker([this.markers[i].latitude, this.markers[i].longitude], this.markerOption);
marker.addTo(this.map);
marker.bindPopup(`
<h6 class="display-6">${this.markers[i].derivation_code}</h6>
<img src="storage/images/${this.markers[i].image}" style="height:100%;width:100%">
`);
}
},
markerClicked: function(mrkr_data){
this.selectedSample = mrkr_data.derivation_code;
console.log(this.selectedSample);
}
},
});

How to set image src as a string in vue component?

I've created two components to send an image in base-64 encoded format to a server. When the parent component is mounted it's supposed to set the child reference to file.
Vue.component('some-form', {
template: '#some-form',
data: function() {
return {
logoImage: '',
coverImage: ''
}
},
methods: {
onSubmit: function(event) {
var dataForm = {};
var that = this;
dataForm['logo-image'] = this.logoImage;
dataForm['cover-image'] = this.coverImage;
// AJAX REQUEST HERE with posting data
},
},
mounted: function(){
var $this = this;
// AJAX REQUEST HERE with getting data
}
});
Vue.component('upload-photo', {
template: '#upload-photo',
data: function () {
return {
image: {
body: '',
'content-type': '',
'content-length': '',
url: ''
},
imageBody: ''
}
},
props: ['logoImage', 'title', 'description'],
watch: {
'image': function() {
this.$emit('input', this.image);
}
},
created: function(){
this.image = this.logoImage;
},
mounted: function () {
var that = this;
//AJAX REQUEST HERE to get data
},
methods: {
onFileChange: function(e) {
var files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.createImage(files[0]);
},
createImage: function(file){
var image = new Image();
var reader = new FileReader();
var vm = this;
vm.image = {};
reader.onload = function(e) {
vm.image.body = e.target.result;
vm.imageBody = e.target.result;
};
vm.$set(vm.image, 'content-type', file.type);
vm.$set(vm.image, 'content-length', file.size);
reader.readAsDataURL(file);
},
removeImage: function (e) {
this.image = '';
}
}
});
var app = new Vue({
el: '#app',
data: function() {
},
methods: {
},
mounted: function() {
}
});
Full example https://codepen.io/anon/pen/ZvzwzO
How can it be implemented?
P.S. I have no idea how to implement it in the same component. I send data as a string with two more property, however get as a string to, however it's link.
P.S.S. need just way to search.
It is difficult to tell exactly what you are asking but it sounds like you want to pass data from the parent component to the child. If you haven't already, read about Composing components and Dynamic Props for passing properties from a parent component to a child component.
One way to do this is to make the imageBody a property of the upload-photo component instead of part of the data.
props: ['logoImage', 'title', 'description', 'imageBody'],
Then have the parent supply a value for that property:
<upload-photo v-model="logoImage" title="TITLE 1" description="description_1" v-bind:image-body="imageBody">
Take a look at this phpfiddle. When the form is mounted, it sends an AJAX call back to the server to retrieve a URL, then sets the property on that first upload-photo child element to the URL sent back from the server in the AJAX response. Note that the upload-photo template was changed to show the image if imageBody is truthy instead of image.

How can I handle a ajax request response in the Flux Architecture?

Looking at the Flux Documentation I can't figure out how the code to a ajax update, and a ajax fetch would fit into the dispatcher, store, component architecture.
Can anyone provide a simple, dummy example, of how an entity of data would be fetched from the server AFTER page load, and how this entity would be pushed to the server at a later date. How would the "complete" or "error" status of request be translated and treated by the views/components? How would a store wait for the ajax request to wait? :-?
Is this what you are looking for?
http://facebook.github.io/react/tips/initial-ajax.html
you can also implement a fetch in the store in order to manage the information.
Here is an example (it is a concept, not actually working code):
'use strict';
var React = require('react');
var Constants = require('constants');
var merge = require('react/lib/merge'); //This must be replaced for assign
var EventEmitter = require('events').EventEmitter;
var Dispatcher = require('dispatcher');
var CHANGE_EVENT = "change";
var data = {};
var message = "";
function _fetch () {
message = "Fetching data";
$.ajax({
type: 'GET',
url: 'Url',
contentType: 'application/json',
success: function(data){
message = "";
MyStore.emitChange();
},
error: function(error){
message = error;
MyStore.emitChange();
}
});
};
function _post (myData) {
//Make post
$.ajax({
type: 'POST',
url: 'Url',
// post payload:
data: JSON.stringify(myData),
contentType: 'application/json',
success: function(data){
message = "";
MyStore.emitChange();
},
error: function(error){
message = "update failed";
MyStore.emitChange();
}
});
};
var MyStore = merge(EventEmitter.prototype, {
emitChange: function () {
this.emit(CHANGE_EVENT);
},
addChangeListener: function (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getData: function (){
if(!data){
_fetch();
}
return data;
},
getMessage: function (){
return message;
},
dispatcherIndex: Dispatcher.register( function(payload) {
var action = payload.action; // this is our action from handleViewAction
switch(action.actionType){
case Constants.UPDATE:
message = "updating...";
_post(payload.action.data);
break;
}
MyStore.emitChange();
return true;
})
});
module.exports = MyStore;
Then you need to subscribe your component to the store change events
var React = require('react');
var MyStore = require('my-store');
function getComments (){
return {
message: null,
data: MyStore.getData()
}
};
var AlbumComments = module.exports = React.createClass({
getInitialState: function() {
return getData();
},
componentWillMount: function(){
MyStore.addChangeListener(this._onChange);
},
componentWillUnmount: function(){
MyStore.removeChangeListener(this._onChange);
},
_onChange: function(){
var msg = MyStore.getMessage();
if (!message){
this.setState(getData());
} else {
this.setState({
message: msg,
data: null
});
}
},
render: function() {
console.log('render');
return (
<div>
{ this.state.message }
{this.state.data.map(function(item){
return <div>{ item }</div>
})}
</div>
);
}
});
I hope it is clear enough.

AngularJS - Strange behaviour of promises in connection with notify()

As I want to implement a chat in AngularJS, I want to use the promise/deferred principle. My ChatService looks like the following:
factory('ChatService', ['$q', '$resource', function($q, $resource) {
var Service = {};
var connected = false;
var connection;
var chatResource = $resource('/guitars/chat/:action', {action: '#action'}, {
requestChatroomId: {
params: {
action: 'requestChatroomId'
},
method: 'GET'
},
sendMessage: {
params: {
action: 'sendMessage'
},
method: 'POST'
}
});
Service.connect = function(cb) {
var deferred = $q.defer();
chatResource.requestChatroomId(function(data) {
connection = new WebSocket('ws://127.0.0.1:8888/realtime/' + data.chatroomId);
connection.onerror = function (error) {
deferred.reject('Error: ' + error);
};
connection.onmessage = function (e) {
cb.call(this, e.data);
deferred.notify(e.data);
};
connected = true;
});
return deferred.promise;
};
Service.sendMessage = function(msg) {
if(!connected) {
return;
}
chatResource.sendMessage({message: msg});
}
return Service;
}])
My controller using the ChatService is:
app.controller('ChatCtrl', ['$scope', 'ChatService', function($scope, ChatService) {
$scope.chat = {};
$scope.chat.conversation = [];
var $messages = ChatService.connect(function(message) {
$scope.$apply(function() {
// #1 THIS FIRES EVERY TIME
$scope.chat.conversation.push(message);
});
});
$messages.then(function(message) {
console.log('Finishes - should never occur!')
}, function(error) {
console.log('An error occurred!')
}, function(message) {
// #2 THIS FIRES ONLY IF THERE IS AN INTERACTION WITH THE ANGULAR MODEL
console.log(message);
});
$scope.sendMessage = function(event) {
ChatService.sendMessage($scope.chat.message);
$scope.chat.message = '';
};
}]);
If something is pushed from the server, callback #1 is called, but callback #2 wont be called until there is some interaction with the angular-model, i.e. start writing something in the input-Box. What is the reason for that behaviour?
Okay the reason was, that AngularJS was not aware of a change. So I injected the $rootScope to my ChatService:
factory('ChatService', ['$q', '$resource', '$rootScope', function($q, $resource, $rootScope) {
and in connection.onmessage I called $apply() on $rootScope:
connection.onmessage = function (e) {
deferred.notify(e.data);
$rootScope.$apply();
};

Resources