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>)
},
Related
I am New to React Redux.This is a simple App I am developing.But Error is I can't connect to store through Action.
This is my error
utils.js:78 Uncaught Error: action is missing a listen method
topic-list.js
var React = require('react');
var Reflux = require('reflux');
var TopicStore = require('../stores/topic-store');
var Actions = require('../actions');
module.exports = React.createClass({
mixins:[
Reflux.listenTo(TopicStore,'onChange')
],
getInitialState:function () {
return{
topics:[]
}
},
componentWillMount:function () {
//Actions.getTopics()
Actions.getTopics()
},
render:function () {
return <div className="list-group">
ToPic List
{this.renderTopics()}
</div>
},
renderTopics:function () {
return this.state.topics.map(function(topic) {
return <li key={topic.id}>
{topic.description}
</li>
})
},
onChange:function (evet,topics) {
this.setState({topics:topics})
}
});
actions.js
var Reflux = require('reflux');
module.exports = Reflux.createAction([
'getTopics',
]);
topic-store.js
var Api = require('../utils/api');
var Reflux = require('reflux');
var Actions = require('../actions');
module.exports = Reflux.createStore({
listenables:[Actions],
getTopics:function () {
return Api.get('topics/defaults')
.then(function (json) {
this.topics = json.data;
this.triggerChange();
}.bind(this));
},
con:function () {
console.log('Working')
},
triggerChange:function () {
this.trigger('change',this.topics)
}
});
The error message implies you are trying to listen to something which can't be listened to (ie. it doesn't have a listen method.)
That means the source of the error can either be:
listenables:[Actions] from topic-store.js
Reflux.listenTo(TopicStore,'onChange') from topic-list.js
I believe it's the former, as there is a typo in actions.js, as the method is Reflux.createActions, not Reflux.createAction.
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.
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
This is my component class (Part of it).
updateStore: function() {
console.log("Updating state in the Calendar.js");
this.setState(this.getInitialState());
},
componentDidMount: function() {
EventsStore.addChangeListener(this.updateStore, 'CHANGE');
},
componentDidUnmount: function() {
EventStore.removeChangeListener(this.updateStore);
},
This is my action (Tuxx)
var Actions = require('tuxx/Actions');
var eventsStore = require('../Stores/EventsStore');
var jQ = require('jquery');
var eventsActions = Actions.createActionCategory({
category: 'events',
source: 'standard',
actions: ['create', 'get']
});
eventsActions.register(eventsStore, {
create: eventsStore.onCreate,
get: eventsStore.onGet
});
eventsActions.before('get', function (nextCallback, actionBody) {
jQ.get('http://127.0.0.1:8181/events').done(function(resp) {
nextCallback(resp);
});
});
module.exports = eventsActions;
And this is part of my store
onGet: function(resp) {
resp = JSON.parse(resp);
this._events = resp;
console.log(this._events);
console.log("Emiting change")
this.emitChange('CHANGE');
},
And last, this is my init code:
eventsAction.get();
var App = React.createClass({
render: function() {
return (
<div>
<RouteHandler />
</div>
)
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<DefaultRoute handler={Calendar} />
<Route name="event.edit" path="/event/:eventId" handler= {EventEditForm} />
</Route>
);
Router.run(routes, function(Handler) {
React.render(<Handler />, document.getElementById("main"));
});
As far as I understand, it should re render my component when emitChange is run.
This is my console output:
I think it should hit the
console.log("Updating state in the Calendar.js");
part, but it doesn't.
I am far from being competent in JS world, so I need help.
Thank you in advance.
This is how store is required:
var EventsStore = require('./Stores/EventsStore');
store is saved as follows:
Store is defined as:
var Stores = require('tuxx/Stores')
var eventsStore = Stores.createStore({
_events: [],
getAll: function () {
return Object.keys(this._events);
},
(...)
(...)
onGet: function(resp) {
resp = JSON.parse(resp);
this._events = resp;
console.log(this._events);
console.log("Emiting change")
this.emitChange();
},
register: function () {
return {
events: {
create: this.onCreate,
get: this.onGet
}
};
}
});
module.exports = eventsStore;
In the component I use it using EventsStore variable which was created from:
var EventsStore = require('./Stores/EventsStore');
Second edit.
I was still digging and I found out this:
componentDidMount: function() {
EventsStore.addChangeListener(this.updateStore);
console.log('Calendar::componentDidMount');
console.log(EventsStore.listeners());
console.log('----')
},
And the result in the console is:
[Log] Calendar::componentDidMount (app.js, line 36083)
[Log] [] (app.js, line 36084)
Having looked at the Tuxx source code, I believe that should work. You can also omit that second "CHANGE" parameter to emitChange and addChangeListener and it'll use a default. I assume it's the same instance of the store you're using everywhere?
Looking at Tuxx, they use the createOwnerClass and connectOwnerToStore combo to make all of this happen automatically - see the initial guide on the homepage. Perhaps using that approach would help you track down the bug?
I'm working on a React app with Reflux and I'm having a problem connecting a store to a component.
Here's the code
// src/stores/post.js
var Reflux = require('reflux');
var $ = require('jquery');
var PostActions = require('../actions/post');
module.exports = Reflux.createStore({
init: function() {
this.listenTo(PostActions.vote, this.onVote);
},
getInitialData: function() {
return {
title: "Post 1",
content: "This is a post!",
voteCount: 6
}
},
onVote: function(postId, studentId, voteValue) {
this.trigger();
console.log("VOTE ACTION TRIGGERED");
}
});
// src/actions/post.js
var Reflux = require('reflux');
module.exports = Reflux.createActions([
"vote"
]);
// src/components/posts/upvote.js
var React = require('react');
var Reflux = require('reflux');
var PostStore = require('../../stores/post');
var PostActions = require('../../actions/post');
module.exports = React.createClass({
mixins: [Reflux.ListenerMixin],
getInitialState: function() {
return {
voteCount: this.props.votes
}
},
componentDidMount: function() {
this.listenTo(PostStore, this.onVoteCountChange);
},
componentWillUnmount: function() {
this.unsubscribe();
},
onVoteCountChange: function(newVoteCount) {
this.setState({
voteCount: newVoteCount
});
},
handleClick: function() {
console.log(PostActions);
PostActions.vote(
null, null, null
);
},
render: function() {
return (
<div className="votes">
<p>{this.state.voteCount}</p>
<span className="glyphicon glyphicon-chevron-up"
onClick={this.handleClick}></span>
</div>
)
}
});
The problem is, the code works when I run it in the Node console:
> var PostStore = require('./src/stores/post');
undefined
> var PostActions = require('./src/actions/post');
undefined
> PostActions.vote(null, null, null);
undefined
> VOTE ACTION TRIGGERED
But when I run the tests, the event doesn't get logged. However, I know the click is happening because handleClick() is being called and the PostActions object is being printed to the console.
The PostStore is also being initialized (I had a console.log() in there to verify it). This leads me to believe that somehow the problem is in the React component, but as far as I can tell my code looks exactly like what's in the Reflux documentation.
Also, as an aside, is there a better way for me to debug my code during the Jest tests than with a bunch of console.log() calls thrown everywhere? Something like binding.pry in ruby?
EDIT: I'm including the tests:
jest.dontMock('../../../src/components/posts/upvote');
jest.dontMock('../../../src/actions/post.js');
jest.dontMock('../../../src/stores/post.js');
describe('Upvote', function() {
var React = require('react/addons');
var Upvote = require('../../../src/components/posts/upvote');
var TestUtils = React.addons.TestUtils;
var upvote;
beforeEach(function() {
upvote = TestUtils.renderIntoDocument(
<Upvote postId="1" votes="6"/>
);
});
it('should display the correct upvote count', function() {
var votes = TestUtils.findRenderedDOMComponentWithTag(
upvote, "p"
).getDOMNode().textContent;
expect(votes).toEqual("6");
});
it('should handle upvote clicks', function() {
var upArrow = TestUtils.findRenderedDOMComponentWithTag(
upvote, "span"
).getDOMNode();
TestUtils.Simulate.click(upArrow);
// var votes = TestUtils.findRenderedDOMComponentWithTag(
// upvote, "p"
// ).getDOMNode().textContent;
// expect(votes).toEqual("7");
});
});
So as it turns out, I had two problems. The first one was that reflux was being mocked automatically. The second had to do with actions and timers, and I found the solution here.
I'm gonna post my code anyways:
// gulpfile.js
// the config is used for gulp-jest
var jestConfig = {
"scriptPreprocessor": "./helpers/jsx-preprocessor.js", // relative to gulp.src
"unmockedModulePathPatterns": [
"../node_modules/react",
"../node_modules/reflux" // this is where reflux gets unmocked
]
}
// __tests__/upvote.js
it('should handle upvote clicks', function() {
var upArrow = TestUtils.findRenderedDOMComponentWithTag(
upvote, "span"
).getDOMNode();
TestUtils.Simulate.click(upArrow);
jest.runAllTimers(); // this is where the magic happens
var votes = TestUtils.findRenderedDOMComponentWithTag(
upvote, "p"
).getDOMNode().textContent;
expect(votes).toEqual("7");
});