I have a pretty simple Fluxible store:
export default class NoteStore extends BaseStore {
static storeName = "NoteStore";
static handlers = {
[Actions.NEW_NOTES_FETCHED]: 'handleNewNotes'
};
constructor(dispatcher) {
super(dispatcher);
this.notes = [];
}
handleNewNotes(notes) {
this.notes = [];
var i;
for (i = 0; i < notes.length; i++){
this.notes.push(notes[i].content);
}
this.emitChange();
}
/* ... */
dehydrate() {
return { notes: this.notes };
}
rehydrate(state) {
this.notes = state.notes;
}
// Shouldn't be necessary to override?
shouldDehydrate() {
return true;
}
}
NEW_NOTES_FETCHED is dispatched by an action that gets data from my backend API, and the store listens for that event and pulls in the data from the payload. As far as I can tell, all this is working because everything runs perfectly when running in the client.
The problem I'm having is that the NoteStore doesn't seem to be getting dehydrated when the server calls app.dehydrate(). I look at the JSON embedded into the page and I don't see my store anywhere, though I do see information for the RouteStore.
I registerd my store with the FluxibleContext, but do I need to do something additionally to add it to the dehydrate chain?
App bootstrapping code if relevant:
const app = new Fluxible({ component: Root });
const AppRouteStore = RouteStore.withStaticRoutes(routes);
app.registerStore(AppRouteStore); // AppRouteStore appears in dehydrated JSON
app.registerStore(HtmlHeadStore); // Neither of these do, though HtmlHeadStore doesn't need to
app.registerStore(NoteStore);
export default app;
Ok, I figured out what was wrong. Basically, the action that was supposed to dispatch the NEW_NOTES_FETCHED event wasn't returning a promise and so the logic of handling the response from the backend server was never actually run, even though the request itself was made and I saw it appear on the backend's logs.
I was about to tear my hair out puzzling over this for so long, so hopefully someone can learn from my struggle!
Related
I am unable to resolve the following problem at the moment.
1.When you have successfully logged in, the following code will be called in Layout.vue to create a state. (You will then go directly to: /dashboard)
mounted() {
this.$store.dispatch(DCOMPONENTS);
}
2.Then I go to /sales and a dispatch is done again within the Sales.vue component:
methods: {
async loadTable() {
await this.$store
.dispatch(SALES)
let tableName = 'SalesTable';
var components = this.getComponents;
var sales = this.getSales;
var i;
for (i = 0; i < components.length; i++) {
if (components[i].name == tableName) {
var structure = components[i];
}
}
var jsonData = {};
jsonData.structure = structure;
jsonData.data = sales;
this.structureTable = jsonData;
this.dynamicComponent = Tables;
},
Normally this goes well. When I am in / dashboard, time is lost navigating to / sales, so that the first AXIOS call is also ready, when the second AXIOS call via (dispatch) is also ready in the component, I have all the data.
However, the problem:
When I am logged in and I am in /sales as a route. Press F5 (refresh), then it may be in some cases that the first AXIOS call (from Layout.vue) is not yet ready while the AXIOS call via (Sales.vue) is ready. I am missing data so that my Array is not filled.
My question:
How can I ensure that the first AXIOS call made in Layout.vue (which stores the first data in STATE) is always ready before I make a new AXIOS call (dispatch) in Sales.vue?
So after making this function work I started to create a loop that would give me feedback from the backend after SSR, I wanted to use hooks so I made it a functional component and started to write but the hook (even with nothing in it) is throwing 2 errors. Invalid Hook Call and A cross origin error was thrown.
I tried changing the file name to jsx, moving the file out of the folder I had because there was a second node modules in there (I thought it was using two versions of React), I also read somewhere just to clear local storage and it was just a in development using localhost problem.
*Edit So i've found that its not even calling the fn: reactToPdfUtils.savePDFNOW(sourceElement, true, undefined, cb) its stopping here
//reactToPdf.js
import React, {useEffect} from 'react';
import { savePDF } from '#progress/kendo-react-pdf';
import { drawDOM, exportPDF } from '#progress/kendo-drawing';
var ClassInstancesStore = require('../libs/goggles/reflux/classInstances-store');
var ClassInstancesActions = require('../libs/goggles/reflux/classInstances-actions');
export const savePDFNOW = (sourceElement, willSaveToDB, pageTemplate, cb) => {
//this hook broke the program as soon as i put it in even with nothing inside
useEffect(() => {
//Functionthat gets called after sending the pdf to the backend
// function onClassInstancesStoreChange(opInfo){
// var e = cloneDeep(opInfo);
// if (e.op === 'Call::StorePassportPDFToDisk') {
// if(e.error){
// console.log(e.ret)
// setPdf({ pdfErrors: e.ret })
// } else {
// console.log(e.ret)
// setPdf({ inProgress: true })
// alert('Successfully created: ' + e.ret.fileName)
// // onSubmit()
// }
// }
// };
// let listeners = [];
// listeners.push(ClassInstancesStore.listen(onClassInstancesStoreChange));
// return function cleanup() {
// _.each(listeners, function(listener) {listener();}); //NOTE: destroy listeners
// }
}, [])
try {
//do all the my functions that make my pdf perfect
} catch (error) {
//snap something went wrong all my awesome error handling
}
};
//previewer.jsx
var React = require('react');
var _ = require('underscore');
var reactToPdfUtils = require('../../../../../components/reactToPdf.js');
handleSave = (sourceElement) => {
reactToPdfUtils.savePDFNOW(sourceElement, true, undefined, cb)
function cb(sendDataContent){
if(sendDataContent.err){
console.log(sendDataContent.message)
} else {
console.log('sucess')
}
}
};
My understanding of the code is that the function handleSave will call the external hook savePDFNOW. If this is what happens, then this will break regardless of the useEffect logic.
The reason for that is that hooks that are extracted outside of the component require their name to start with use
So to allow the hook to run you change its name to useSavePDFNOW.
That being said, I believe this is not a valid use case for useEffect, think of useEffect as componentDidMount/Update. This is relevant to component render cycle rather than event listeners. It makes more sense to do away with the useEffect and keep it a regular function.
A few more things, if you are using the latest react version you don't need to import react. Also it's recommended to use const/let instead of var as well.
I'm new to react/flux architecture, and I'm missing something...I think. I have two Stores, SubjectsStore.js and WorkDoneStore.js with an AppActions which does the dispatch (code snippets all below). I'm under the impression that any Store that registers with the AppDispatcher will get notice of the event, and it is incumbent on each store to handle the proper action types. There doesn't seem to be any other way of controlling which Store gets called. In my case, I've gotten as far as getting one the SubjectStores registration to be called, but my WorkDoneStore is not getting called. What am I overlooking / doing wrong.
AppActions.js
import AppDispatcher from './AppDispatcher.js';
import WorkDoneConstants from '../constants/WorkDoneConstants.js';
import SubjectConstants from '../constants/SubjectConstants.js';
var AppActions = {
addWorkDoneItem:function(item){
console.log("In app actions addWorkDone");
console.log(WorkDoneConstants.WORKDONE_INSERT);
AppDispatcher.dispatch({
actionType:WorkDoneConstants.WORKDONE_INSERT,
item:item
})
}
}
module.exports = AppActions;
SubjectsStore.js
var AppDispatcher = require('../dispatcher/AppDispatcher');
var SubjectConstants = require('../constants/SubjectConstants');
var EventEmitter = require('events').EventEmitter;
...
AppDispatcher.register(function(action) {
var text;
console.log("why am I in the subjectStore?");
console.log(action.actionType);
console.log(action.item);
switch(action.actionType) {
case SubjectConstants.SUBJECT_CREATE:
text = action.text.trim();
...
WorkDoneStore.js
...
AppDispatcher.register(function(action) {
var text;
console.log("In WorkDoneStore");
console.log(action);
switch(action.actionType) {
case WorkDoneConstants.WORKDONE_INSERT:
item = action.item;
if (item.subject !== '') {
create(item);
WorkDoneStore.emitChange();
}
break;
...
My component
...
handleSubmit: function(e){
e.preventDefault();
var item = {
subject:this.state.subject,
workDone:this.state.workDone,
minutes:this.state.totalMinutes,
startStop:this.state.startStop,
};
console.log("before AppActions.");
AppActions.addWorkDoneItem(item);
},
...
In looking through my Webpack output I noticed that the WorkDoneStore.js wasn't getting included. By forcing it to be included via a call to it, it's now working.
I'm upgrading/rewriting an existing angular app to use angular2. My problem is that I want to open a OAuth flow in a new pop up window and once the OAuth flow is completed use window.postMessage to communicate back to the angular 2 app that the OAuth flow was successful.
Currently what I have is in the angular 2 service is
export class ApiService {
constructor(private _loggedInService: LoggedInService) {
window.addEventListener('message', this.onPostMessage, false);
}
startOAuthFlow() {
var options = 'left=100,top=10,width=400,height=500';
window.open('http://site/connect-auth', , options);
}
onPostMessage(event) {
if(event.data.status === "200") {
// Use an EventEmitter to notify the other components that user logged in
this._loggedInService.Stream.emit(null);
}
}
}
This template that is loaded at the end of the OAuth flow
<html>
<head>
<title>OAuth callback</title>
<script>
var POST_ORIGIN_URI = 'localhost:8000';
var message = {"status": "200", "jwt":"2"};
window.opener.postMessage(message, POST_ORIGIN_URI);
window.close();
</script>
</head>
</html>
Using window.addEventListener like this seems to completely break the angular 2 app, dereferencing this.
So my question is can I use window.addEventListener or should I not use postMessage to communicate back to the angular2 app?
** Complete angular2 noob so any help is appreciated
I have a complete Angular2 OAuth2 skeleton application on Github that you can refer to.
It makes use of an Auth service for OAuth2 Implicit grants that in turn uses a Window service to create the popup window. It then monitors that window for the access token on the URL.
You can access the demo OAuth2 Angular code (with Webpack) here.
Here is the login routine from the Auth service, which will give you an idea of what's going on without having to look at the entire project. I've added a few extra comments in there for you.
public doLogin() {
var loopCount = this.loopCount;
this.windowHandle = this.windows.createWindow(this.oAuthTokenUrl, 'OAuth2 Login');
this.intervalId = setInterval(() => {
if (loopCount-- < 0) { // if we get below 0, it's a timeout and we close the window
clearInterval(this.intervalId);
this.emitAuthStatus(false);
this.windowHandle.close();
} else { // otherwise we check the URL of the window
var href:string;
try {
href = this.windowHandle.location.href;
} catch (e) {
//console.log('Error:', e);
}
if (href != null) { // if the URL is not null
var re = /access_token=(.*)/;
var found = href.match(re);
if (found) { // and if the URL has an access token then process the URL for access token and expiration time
console.log("Callback URL:", href);
clearInterval(this.intervalId);
var parsed = this.parse(href.substr(this.oAuthCallbackUrl.length + 1));
var expiresSeconds = Number(parsed.expires_in) || 1800;
this.token = parsed.access_token;
if (this.token) {
this.authenticated = true;
}
this.startExpiresTimer(expiresSeconds);
this.expires = new Date();
this.expires = this.expires.setSeconds(this.expires.getSeconds() + expiresSeconds);
this.windowHandle.close();
this.emitAuthStatus(true);
this.fetchUserInfo();
}
}
}
}, this.intervalLength);
}
Feel free to ask if you have any questions or problems getting the app up and running.
So with a bit of investigation found out the problem. I was de-referencing this. This github wiki helped me understand it a bit more.
To solve it for my case needed to do a couple of things. Firstly I created a service that encapsulated the adding of an eventListener
import {BrowserDomAdapter} from 'angular2/platform/browser';
export class PostMessageService {
dom = new BrowserDomAdapter();
addPostMessageListener(fn: EventListener): void {
this.dom.getGlobalEventTarget('window').addEventListener('message', fn,false)
}
}
Then using this addPostMessageListener I can attach a function in my other service to fire
constructor(public _postMessageService: PostMessageService,
public _router: Router) {
// Set up a Post Message Listener
this._postMessageService.addPostMessageListener((event) =>
this.onPostMessage(event)); // This is the important as it means I keep the reference to this
}
Then it works how I expected keeping the reference to this
I think this is the Angular2 way:
(Dart code but TS should be quite similar)
#Injectable()
class SomeService {
DomAdapter dom;
SomeService(this.dom) {
dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
}
}
I fiddled around with this for ages but in the end, the most robust way for me was to redirect the user to the oath page
window.location.href = '/auth/logintwitter';
do the oath dance in the backend (I used express) and then redirect back to a receiving front end page...
res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);
There are some idiosyncracies to my solution because e.g. I wanted to use only JsonWebToken on the client regardless of login type, but if you are interested, whole solution is here.
https://github.com/JavascriptMick/learntree.org
My AngularJS CRUD application processes it's information over a WebSocket Server. (This was mainly so that updates from one user would get automatically pushed to all users without the need for massive HTTP polling)
I realized early on that I would have to set up my services differently than I normally do with HTTP services. Normally, for each Model that I am working with, I give them their own service to populate that particular Model. However, this is not feasible with a Websocket Connection, because I don't want a separate connection for each service. Therefore, there are a couple of solutions.
1) set up a single service that establishes a connection, then share that connection with other services that will use that service to make their specific queries
2) make a single, type-agnostic service that will be used by all controllers that need access to the connection and data.
Option 2 seemed much easier to manage and would be reusable across applications, so I started on that. That was when I realized that this was actually an opportunity. Rather than explicitly creating models for each type of data that the Client could receive, I could create a master data object, and dynamically create child objects of myService.data as needed when data flows in from requests. Thus, if I ever need to update my Model, I just update the Model at the server level, and the client already knows how to receive it; it will just need a Controller that knows how to use it.
However, this opportunity brings a drawback. Apparently, because myService.Data is an empty, childless object at creation, any Scope that wants to reference its future children have to simple reference the object itself.
For example, $scope.user = myService.data.user throws an error, because that object doesn't exist at declaration. it would appear that my only option is for each controller to simply have $scope.data = myservice.data, and the view for each controller will simply have to use
< ng-model='data'>, with the declarations being something like {{data.user.username}}. I have tested it, and this does work.
My question is this; Is there any way I can get the best of both worlds? Can I have my service update it's data model dynamically, yet still have my controllers access only the part that they need? I? I was feeling quite clever until I realized that all of my Controllers were going to have access to the entire data model... But I honestly can't decide if that is even a huge problem.
Here is my Service:
app.factory('WebSocketService', ['$rootScope', function ($rootScope) {
var factory = {
socket: null,
data: {},
startConnection: function () {
//initialize Websocket
socket = new WebSocket('ws://localhost:2012/')
socket.onopen = function () {
//todo: Does anything need to happen OnOpen?
}
socket.onclose = function () {
//todo: Does anything need to happen OnClose?
}
socket.onmessage = function (event) {
var packet = JSON.parse(event.data);
////Model of Packet:
////packet.Data: A serialised Object that contains the needed data
////packet.Operation: What to do with the Data
////packet.Model: which child object of Factory.data to use
////packet.Property: used by Update and Delete to find a specific object with a property who's name matches this string, and who's value matches Packet.data
//Deserialize Data
packet.Data = JSON.parse(packet.Data);
//"Refresh" is used to completely reload the array
// of objects being stored in factory.data[packet.Model]
// Used for GetAll commands and manual user refreshes
if (packet.Operation == "Refresh") {
factory.data[packet.Model] = packet.Data
}
//Push is used to Add an object to an existing array of objects.
//The server will send this after somebody sends a successful POST command to the WebSocket Server
if (packet.Operation == "Push") {
factory.data[packet.Model].push(packet.Data)
}
if (packet.Operation == "Splice") {
for (var i = 0; i < factory.data[packet.Model].length; i++) {
for (var j = 0; j < packet.Data.length; j++){
if (factory.data[packet.Model][i][packet.Property] == packet.Data[j][packet.Property]) {
factory.data[packet.Model].splice(i, 1);
i--;
}
}
}
}
// Used to update existing objects within the Array. Packet.Data will be an array, although in most cases it will usually only have one value.
if (packet.Operation == "Update") {
for (var i = 0; i < factory.data[packet.Model].length; i++) {
for (var j = 0; j < packet.Data.length; j++) {
if (factory.data[packet.Model][i][packet.Property] == packet.Data[j][packet.Property]) {
factory.data[packet.Model][i] = packet.Data[j]
i--;
}
}
}
}
//Sent by WebSocket Server after it has properly authenticated the user, sending the user information that it has found.
if (packet.Operation == "Authentication") {
if (packet.Data == null) {
//todo: Authentication Failed. Alert User Somehow
}
else {
factory.data.user = packet.Data;
factory.data.isAuthenticated = true;
}
}
$rootScope.$digest();
}
},
stopConnection: function () {
if (socket) {
socket.close();
}
},
//sends a serialised command to the Websocket Server according to it's API.
//The DataObject must be serialised as a string before it can be placed into Packet object,which will also be serialised.
//This is because the Backend Framework is C#, which must see what Controller and Operation to use before it knows how to properly Deserialise the DataObject.
sendPacket: function (Controller, Operation, DataObject) {
if (typeof Controller == "string" && typeof Operation == "string") {
var Data = JSON.stringify(DataObject);
var Packet = { Controller: Controller, Operation: Operation, Data: Data };
var PacketString = JSON.stringify(Packet);
socket.send(PacketString);
}
}
}
return factory
}]);
Here is a Simple Controller that Accesses User Information. It is actually used in a permanent header <div> in the Index.html, outside of the dynamic <ng-view>. It is responsible for firing up the Websocket Connection.
App.controller("AuthenticationController", function ($scope, WebSocketService) {
init();
function init() {
WebSocketService.startConnection();
}
//this is the ONLY way that I have found to access the Service Data.
//$scope.user = WebSocketService.data.user doesn't work
//$scope.user = $scope.data.user doesn't even work
$scope.data = WebSocketService.data
});
And here is the HTML that uses that Controller
<div data-ng-controller="AuthenticationController">
<span data-ng-model="data">{{data.user.userName}}</span>
</div>
One thing you could do is store the data object on the root scope, and set up watches on your various controllers to watch for whatever controller-specific keys they need:
// The modules `run` function is called once the
// injector is finished loading all its modules.
App.run(function($rootScope, WebSocketService) {
WebSocketService.startConnection();
$rootScope.socketData = WebSocketService.data;
});
// Set up a $watch in your controller
App.controller("AuthenticationController", function($scope) {
$scope.$watch('socketData.user', function(newUser, oldUser) {
// Assign the user when it becomes available.
$scope.user = newUser;
});
});