React Flux Store emitChange Not Firing in Both Components - javascript

I have two different components that leverage the same AuthStore, FacebookUser and Quiz.
The FacebookUser component updates the AuthStore by executing the login action and that actions causes it to execute emitChange. However, the onChange handler for the Quiz does not fire.
AuthStore.js
console.log("AuthStore::CREATED");
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var AuthConstants = require('../constants/AuthConstants');
var assign = require('object-assign');
var CHANGE_EVENT = 'change';
var _user = undefined;
var AuthStore = assign({}, EventEmitter.prototype, {
isLoggedIn: function() {
return _user !== undefined;
},
getUser: function() {
return _user;
},
emitChange: function() {
console.log('AuthStore::emitChange');
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
console.log('AuthStore::addChangeListener', callback);
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
// Register callback to handle all updates
AuthStore.dispatchToken = AppDispatcher.register(function(action) {
console.log('AuthStore::action', action);
switch(action.actionType) {
case AuthConstants.AUTH_LOGIN:
_user = action.user;
AuthStore.emitChange();
break;
case AuthConstants.AUTH_LOGOUT:
_user = undefined;
AuthStore.emitChange();
break;
default:
// no op
}
});
module.exports = AuthStore;
FacebookUser.jsx
var AuthActions = require('../../actions/AuthActions');
var AuthStore = require('../../stores/AuthStore');
function componentWillMount() {
AuthStore.addChangeListener(this.onChange);
}
function onChange() {
console.log('FacebookUser::onChange');
var user = AuthStore.getUser();
this.setState(user);
}
function populateUserProfile(userId) {
FB.api('/' + userId, {fields: 'email,name'}, function(response) {
console.log('FacebookUser::populateUserProfile', response);
AuthActions.login(response);
}.bind(this));
}
Quiz.jsx
var AuthStore = require('../../stores/AuthStore');
var QuizStore = require('../../stores/QuizStore');
function componentWillMount() {
AuthStore.addChangeListener(this.onChange);
QuizStore.addChangeListener(this.onChange);
}
function onChange() {
console.log('Quiz::onChange');
var quiz = QuizStore.getQuiz();
if (!quiz.person.email) {
var user = AuthStore.getUser();
quiz.person.email = user.email;
}
this.setState(quiz);
}
AppDispatcher.js
var Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
I thought that the stores were Singleton's, however, it appears that's not the case. I know I'm missing something stupid. Looking forward to the answer!
Console Output
Below you can see that the FacebookUser::onChange fires. I'm pretty sure the issue is that the AuthStore is created for both components. That's pretty visible from the AuthStore::CREATED log.
AuthStore::CREATED
QuizStore::create
AuthStore::addChangeListener onChange() {
console.log('Quiz::onChange');
var quiz = QuizStore.getQuiz();
if (!quiz.person.email) {
var user = AuthStore.getUser();
quiz.person.email = user.email;
}
th…
QuizStore::addChangeListener onChange() {
console.log('Quiz::onChange');
var quiz = QuizStore.getQuiz();
if (!quiz.person.email) {
var user = AuthStore.getUser();
quiz.person.email = user.email;
}
th…
AuthStore::CREATED
AuthStore::addChangeListener onChange() {
console.log('FacebookUser::onChange');
var user = AuthStore.getUser();
this.setState(user);
}
FacebookUser::statusChangeCallback Object {authResponse: Object, status: "connected"}
FacebookUser::populateUserProfile Object {email: "mperren#gmail.com", name: "Michael Perrenoud", id: "10209047315608853"}
AuthActions::login
AuthStore::action Object {actionType: "AUTH_LOGIN", user: Object}
AuthStore::emitChange
FacebookUser::onChange

Rather than exposing the constructor expose an instance call new on your stores and expose that instance. That way it will be a singleton, in tests I expose a non default export so I can reinititialse a store for each test.

So the issue ended up being a bit esoteric. I was including this user component with a different ReactDOM renderer. Effectively like its own application. This is what was causing the problem. Because they were in different scopes they didn't share the same stores. Moving the component to being rendered by the root component for the application fixed it.

Related

How to create Singleton service in Angular 2 and read URL Params

Im reading an url of the app http://localhost/?config=preprod
Im trying to create a Singleton service which reads UrlParameters.js and exposes get(key) method. Which stores config=preprod
Similar below (from my Angular 1.x singleton service)
get: function (key) {
if (!params) {
params = {};
var queryString = window.location.search.substring(1);
_(queryString.split('&')).each(function (param) {
var val = param.split('=');
params[val[0]] = val[1];
});
}
return params[key];
}
Now, I think I also will need access to Route params inside this service in Angular 2, since I cannot do the in Angular 2.
Also, I need to share this UrlParams singleton with another Singleton service called Flag. Which reads Flag.get('config')
Something like below (extracted from my Angular 1.x project)
Flag.js
set: function (flag) {
if (UrlParameter.get(flag)) {
localStorage.setItem(flag, UrlParameter.get(flag));
}
},
get: function (flag) {
return localStorage.getItem(flag);
}
As suggested by #JordanFrankfurt I used Location service, and fits my purpose. Also Thanks to #Günter Zöchbauer for the efforts.
Below is my UrlParams Service which is been also added in NgModule under providers
url-parameter.service.ts
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/filter';
import {LocationStrategy} from '#angular/common';
#Injectable()
export class UrlParameterService {
public params = null;
constructor(private location: LocationStrategy) {}
get(key:string):String {
debugger;
if (!this.params) {
this.params = {};
var queryString = this.location.path();
queryString.split('&').forEach((param) => {
var val = (param.indexOf('?') ? param.slice(param.indexOf('?')+1).split('=') : param.split('='));
this.params[val[0]] = val[1];
});
}
return this.params[key] || false;
}
}
I would try to stay within the Angular 2 conventions, but if you simply want an instance of something you've instantiated outside of Angular 2 conventions, it's pretty simple.
var UrlParameters = function() {
this.instance = this;
this.params = null;
this.get = function(key) {
if (!this.params){
params = {};
var queryString = window.location.search.substring(1);
_(queryString.split('&')).each(function (param) {
var val = param.split('=');
params[val[0]] = val[1];
});
this.params = params;
}
return this.params[key];
};
this.set = function() {
}
}
var Flag = {
set: function (flag) {
var urlParams = UrlParameter.getInstance();
if (urlParams.get(flag)) {
localStorage.setItem(flag, UrlParameter.get(flag));
}
}
}
TypeScript version
class UrlParameter {
static instance:UrlParameter;
constructor() {
UrlParameter.instance = this;
}
get( key: string) : string {
// ...
}
}
class Flag {
set(key:string) : string {
if (UrlParameter.instance.get(key)){
// you have it
}
}
}
This might do what you want:
#Injectable()
class MyService {
constructor(router:Router) {
this.router.events
.filter(e => e instanceof NavigationEnd)
.forEach(e => {
var config = router.routerState.root.snapshot.param['config'];
console.log(config);
});
}
}

Rendering happens without waiting for API data in React JS

I am new in ReactJS and I am creating single registration page App where drop-down data is from API, but when I am trying to fetch the data there is error showing.
my Sample code is below:
AppApi.js:
var AppActions =require('../actions/AppActions');
var request = require('superagent');
var Data= {};
module.exports ={
request.get('*http://api.randomuser.me/*')
.set('Accept', 'application/json')
.end(function(err, response) {
if (err) return console.error(err);
Data.details= response.text;
AppActions.receiveData(Data.details);
});
}
my Action is:
var AppDispatcher = require('../dispatcher/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var AppActions = {
receiveData: function(data){
AppDispatcher.handleViewAction({
actionType: AppConstants.RECEIVE_DATA,
data: data
})
}
}
module.exports= AppActions;
my ConstantFile is:
module.exports ={
RECEIVE_GENDERS: 'RECEIVE_GENDERS'
}
Dispatcher is:
var Dispatcher = require('flux').Dispatcher;
var assign = require('object-assign');
var AppDispatcher= assign( new Dispatcher(), {
handleViewAction :function(action){
var payload ={
source: 'VIEW_ACTION',
action: action
}
this.dispatch(payload);
}
});
module.exports =AppDispatcher;
in my Store:
var AppDispatcher = require('../dispatcher/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter =require('events').EventEmitter;
var assign =require('object-assign');
var AppAPI = require('../utils/appAPI.js');
var CHANGE_EVENT ='change';
var _data=[];
var AppStore= assign({ }, EventEmitter.prototype, {
setData: function(data){
console.log("my Data", data);
_data=data
},
getData: function(){
//not getting data in console
console.log("returning Data", _data);
return _data;
},
emitChange: function(){
this.emit(CHANGE_EVENT);
},
addChangeListener : function(callback){
this.on('change', callback);
},
removeChangeListener: function(callback){
this.removeListener('change',callback)
}
});
AppDispatcher.register(function(payload){
var action = payload.action;
switch(action.actionType){
case AppConstants.RECEIVE_DATA:
AppStore.setData(action.data);
AppStore.emit(CHANGE_EVENT);
break;
}
return true;
});
module.exports =AppStore;
my Main.js is:
var App= require('./components/App');
var React = require('react');
var ReactDom = require('react-dom');
var AppAPI = require('./utils/appAPI.js');
AppAPI.getGenders();
ReactDom.render(
<App/>,
document.getElementById('app')
)
and My APP.JS is in following format:
var React =require('react');
var AppActions = require('../actions/AppActions');
var AppStore = require('../stores/AppStore');
function getAppState(){
return{
data: AppStore.getData()
}
}
var App= React.createClass({
getInitialState: function(){
console.log(getAppState().data)
return getAppState()
},
componentDidMount: function(){
AppStore.addChangeListener(this._onChange);
},
componentWillUnmount: function(){
AppStore.removeChangeListener(this._onChange);
},
render: function(){
},
_onChange: function(){
this.setState(getAppState());
}
});
module.exports =App;
Problem: empty object getting in console.log(getAppState().data) because rendering part is not waiting for ajax data.
please help me or give me some solution for this problem, I am struggling with it since last 3 days.
Thanks in advance
In case you want to avoid rendering a react component because data are not loaded yet, you can condition the component displaying this way:
return{
<div>
{
this.state.data?
<ComponentExample data={this.state.data}/>
:
null
}
</div>
}
There is other ways to condition component rendering, like using a stateless component, but this ways will work just fine.
Otherwise, It is not a problem that you got an empty object in getInitialState(): It is right that you lack data at first rendering, but as soon as data from your API will be retreived, thanks to _onChange(),the component will be re-rendered and as a result, ComponentExample could be displayed.
function getAppState(){
return{
data: AppStore.getData(),
status:AppStore.getStatus()
}
}
add getStatus function to AppStore
render: function(){
if(this.state.status=='loading')
return (<div className="loading"></div>);
if ....
return (<RealComponent data={this.state.data}></RealComponent>)
},

Flux - Actions being dispatched before Store register

I'm building an app with react using flux as a pattern.
I'm trying to add a loader (spinner) when making an API call but it doesn't work, I guess I'm missing something.
The flow goes like this:
when app is loaded I'm calling initApp
var InitActions = {
initApp: function(){
FooActions.getFoo();
}};
module.exports = InitActions;
FooActions dispatching GET_FOO Action and calling to APIService.foo.getFooByUser
var FooActions = {
getFoo: function(){
var account = accountStore.getAccountData();
var accountId = account.id;
Dispatcher.dispatch({
actionType: ActionTypes.GET_FOO
});
APIService.foo.getFooByUser(accountId);
}};
module.exports = FooActions;
APIService will make an ajax call and in response will trigger ServerActions.getFooSuccess or ServerActions.getFooFailed actions
var APIService = {
foo: {
getFooByUser : function(accountId){
var url = apiUrl;
var params = {
"accountId":accountId
};
promise.post(url,params)
.then(function(response){
ServerActions.getFooSuccess(response);
})
.catch(function(response){
ServerActions.getFooFailed(response);
});
}
}
};
module.exports = APIService;
ServerActions will dispatch GET_FOO_SUCCESS or GET_FOO_Failed
var ServerActions = {
getFooSuccess: function(response){
Dispatcher.dispatch({
actionType: ActionTypes.GET_FOO_SUCCESS,
foo: response
});
},
getFooFailed: function(response){
Dispatcher.dispatch({
actionType: ActionTypes.GET_FOO_FAILED
});
}
}
and the foo store is listening to these events via dispatcher.register
var FooStore = assign({}, EventEmitter.prototype,{...};
Dispatcher.register(function(action){
switch (action.actionType){
case ActionTypes.GET_FOO:
_isLoading = true;
FooStore .emitChange();
break;
case ActionTypes.GET_FOO_SUCCESS:
_isLoading = false;
_foo = action.foo;
FooStore .emitChange();
break;
case ActionTypes.GET_FOO_FAILED:
_isLoading = false;
FooStore.emitChange();
break;
default:
// do nothing
}});
Now, based on the _isLoading param I know when to display and hide loader in my foo component. for some reason the code never getting to the GET_FOO case although this action is dispatching before the API call.
Can someone tell me why?
EDIT:
when I'm debugging the dispatcher's code I can see in dispatch function for loop
Dispatcher.prototype.dispatch = function dispatch(payload) {
!!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.') : invariant(false) : undefined;
this._startDispatching(payload);
try {
for (var id in this._callbacks) {
if (this._isPending[id]) {
continue;
}
this._invokeCallback(id);
}
} finally {
this._stopDispatching();
}
};
I can see the the FooStore haven't been registered yet as a dispatcher callback.
How can I make sure It's being registered before any action is being triggered?
SOLVED:
I'm making sure the FooStore is being registered before any cation by requiring it before I'm calling to initApp()

I am having trouble passing in my backbone collection in to a react component

my backbone collection collection doesn't populate when i just pass it in as props to a react component. I have tried first fetching the collection using componentDidmount and componentWillMount, but that still didn't populate the collection. If I test the code by setting a window variable pointing to DecksIndex and in the console tools call getInstance() and then fetch
,the data loads fine. my code is as follows:
//router.js
var DeckComponent = require("./views/deck.jsx")
var DecksIndex = React.createFactory(require("./views/decks.jsx"))
var decksCollection = require("./component/collections/decks.js");
module.exports = Backbone.Router.extend({
initialize: function(){
this.rootEl = document.getElementById('container');
},
routes: {
"":"index",
"decks/:id":"deckShow"
},
index: function(){
var decks = new DecksIndex({decks: decksCollection.getInstance()});
this._swapView(decks)
console.log("hooray!")
},
deckShow: function(id){
//var deck = Flashcards.Collections.decks.getOrFetch(id);
var showDeck = new DeckComponent();
this._swapView(showDeck);
},
_swapView: function(view){
if (this.currentView) {
React.unmountComponentAtNode(this.rootEl);
}
this.currentView = view
React.render(view, document.getElementById('container'));
}
});
//decks.js
var deck = require('../models/deck.js')
var decks = Backbone.Collection.extend({
url: "/api/decks",
model: deck,
getOrFetch: function(id){
var model = this.get(id);
var that = this;
if (model) {
model.fetch();
}else{
model = new deck({id: id})
model.fetch({
success: function(){
that.add(model)
}
})
}
return model;
},
parse: function (data) {
debugger;
return data.objects
},
});
decks.getInstance = _.memoize(function () {
return new decks();
});
module.exports = decks;
//decks.jsx
var DecksList = React.createClass({
render: function() {
return (
<div className="deck-list">
{
this.props.decks.map(function (deck) {
var title = deck.name
debugger;
return (
<div key={deck.id} className="note-summary">
{title}
</div>
);
})
}
</div>
);
}
});
module.exports = DecksList;
this is an example of a situation where a container component that manages state makes sense. If DecksList had a container that retrieved the collection when it mounted and only rendered DecksList once the data was available it would probably solve the problem. Here's a good article on the pattern: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

How to set data in an action using IronRouter on Meteor?

How can I set additional data in an action function in a Meteor Application that uses IronRouter ? See comments in emailWelcome and emailContract functions below...
Code:
EmailController = RouteController.extend({
template: 'emailPage',
waitOn: function() {
return [
Meteor.subscribe('customers'),
];
},
data: function() {
var request = Requests.findOne(this.params._id);
if (!request)
return;
var customer = Customers.findOne({'_id': request.customerId});
if (!customer)
return;
return {
sender: Meteor.user(),
recipient: Customers.findOne({_id:Session.get('customerId')})
};
},
emailWelcome: function() {
// Set var in the context so that emailTemplate = 'welcomeEmail' here
this.render('emailPage');
},
emailContract: function() {
// Set var in the context so that emailTemplate = 'contractEmail' here
this.render('emailPage');
}
});
You can get access to the data with this.getData() in your action functions:
emailWelcome: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'welcomeEmail';
this.render('emailPage');
},
emailContract: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'contractEmail';
this.render('emailPage');
}
be careful not to call this.data(), as that will regenerate the
data instead of getting you a reference to the already generated data
object.
also be careful not to call this.setData(newData) within an action as that will invalidate the old data object, initiating a reactivity reload, and lead to an infinite loop!

Categories