React, can't set up auto update on emit change - javascript

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?

Related

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>)
},

Using scryRenderedComponentsWithType or findRenderedComponentWithType

I am trying to do some unit testing by using findRenderedComponentWithType and findRenderedComponentWithType to find components. However, I am having a bit of trouble.
I have a shallow rendered component <Layout /> and within it, I want to find <UserMenu />.
var Layout = React.createClass({
render: function(){
console.log(this.props);
return (
<div id="data-trader">
<header className="header">
<UserMenu user={this.props.user} />
</header>
<SideMenu user={this.props.user}/>
</div>
);
}
});
module.exports = Layout;
Within my test file, I tried this:
describe('Layout', function() {
beforeEach(function(){
fakeDOM = TestUtils.createRenderer();
it('should exist as a component', function(done) {
expect(<Layout/>).to.exist;
done();
});
fakeDOM.render(<Layout />);
renderedFakeDOMOutput = fakeDOM.getRenderOutput();
});
it('should have login button when props.user is undefined', function(done) {
renderedFakeDOMOutput.props.user = undefined;
let UserMenuComponent = TestUtils.scryRenderedComponentsWithType(renderedFakeDOMOutput, UserMenu);
done();
});
});
However, scryRenderedComponentsWithType and findRenderedComponentWithType cannot find anything of the components with the type UserMenu.
I also tried to create another file where it exports the UserMenu component but I get the same output or 0 found (Array length 0) or error when no components are found (Error: Did not find exactly one match for componentType:function UserMenu()).
I know it's not a direct solution to the question you're asking, but when I tried shallow rendering, I struggled with it just as you are. React's own docs describe this feature as an early release with "some limitations".
Shallow testing currently has some limitations, namely not supporting
refs. We're releasing this feature early and would appreciate the
React community's feedback on how it should evolve.
I got tired of banging my head against the wall and went with a more straight-forward solution that I found elsewhere.
http://substantial.com/blog/2014/11/11/test-driven-react-how-to-manually-mock-components/
In case that link ever goes bad: Basically, it says to use Rewire to swap out your sub-components with something you create inline in your test. Here is exactly what I do:
rewire-module.js (copied from the blog post)
module.exports = function rewireModule(rewiredModule, varValues) {
var rewiredReverts = [];
beforeEach(function () {
var key, value, revert;
for (key in varValues) {
if (varValues.hasOwnProperty(key)) {
value = varValues[key];
revert = rewiredModule.__set__(key, value);
rewiredReverts.push(revert);
}
}
});
afterEach(function () {
rewiredReverts.forEach(function (revert) {
revert();
});
});
return rewiredModule;
};
MyList.jsx (snippet)
<div>
{ items.map(item => <MyListItem key={item.id} item={item}/>) }
</div>
MyList-test.jsx (snippet)
let rewireModule = require('../utils/rewire-module');
let rewire = require('rewire');
let MyList = rewire('./MyList.jsx');
rewireModule(MyList, {
MyListItem: React.createClass({
propTypes: {
item: React.PropTypes.object.isRequired
},
render: function () {
return <div className="MyListItem"/>;
}
})
});
it('should render data', function () {
// init
MyStore.onLoadCompleted([
{id: 1, name: 'Test 1'},
{id: 2, name: 'Test 2'}
]);
let listComponent = TestUtils.renderIntoDocument(<MyList/>);
let listItems = TestUtils.scryRenderedDOMComponentsWithClass(listComponent, 'MyListItem');
expect(listItems.length).to.equal(2);
// cleanup
React.unmountComponentAtNode(listComponent.getDOMNode());
});
As you may have figured, I basically use "className" as my element identifiers to find them later.
Sometimes if I want to search for specific sub-components with specific data, I'll build something unique into the className.
rewireModule(MyList, {
MyListItem: React.createClass({
propTypes: {
item: React.PropTypes.object.isRequired
},
render: function () {
return <div className={'MyListItem_' + item.id}/>;
}
})
});
it('should render data', function () {
// init
MyStore.onLoadCompleted([
{id: 1, name: 'Test 1'},
{id: 2, name: 'Test 2'}
]);
let listComponent = TestUtils.renderIntoDocument(<MyList/>);
TestUtils.findRenderedDOMComponentWithClass(listComponent, 'MyListItem_1');
TestUtils.findRenderedDOMComponentWithClass(listComponent, 'MyListItem_2');
// cleanup
React.unmountComponentAtNode(listComponent.getDOMNode());
});

Set state after observe()

What's the best way to set state based on the data received from observe()?
It seems setting state via componentWillMount() won't work as observe() runs after this and the data isn't available to set state.
I'm using the observe function as advised when using Parse
E.g.:
var DragApp = React.createClass({
getInitialState: function () {
return {
activeCollection : ''
};
},
observe: function() {
return {
collections: (collectionsQuery.equalTo("createdBy", currentUser))
};
},
_setactiveCollection: function(collection) {
this.setState({
activeCollection : collection
});
},
componentWillMount: function () {
var collection = this.data.collections[0];
this._setActiveCollection(collection);
},
)}
I went the wrong way about this.
I shouldn't be storing this.data into state. I can pass it into components via render.
To get round this.data not being ready before rendering, I make use of the ParseReact function pendingQueries() inside render. E.g.
if (this.pendingQueries().length > 0) {
content = 'loading...'
} else {
content = 'hello world I am' + this.data.name
}
Try:
var DragApp = React.createClass({
observe: function() {
var collections = collectionsQuery.equalTo("createdBy", currentUser);
return {
collections: collections,
activeCollection: collections[0]
};
},
render: function () {
// do something with this.data.collections and/or this.data.activeCollection
},
)}

Reflux event not triggering

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");
});

State null in component

I'm having an issue with creating a component using react and martyjs. I'm sure it is a typo or something but I just can't seem to find it. Although I have a state mixin in the component the state is not being populated and it doesn't look like getState is even being called in the mixin.
Mixin.es6
var StateMixin = Marty.createStateMixin({
listenTo: VideoStore,
getState: function() {
return {
items: VideoStore.list(),
currentItem: VideoStore.select(),
}
}
});
State.es6
var VideoStore = Marty.createStore({
displayName: "Store",
handlers: {
list: Events.List,
render: Events.Render
},
getInitialState: function(){
return { };
},
list: function(){
return this.fetch({
id: 'list',
locally: function(){
if(this.hasAlreadyFetched('list') )
return this.state.items;
},
remotely: function(){
return DissolveStateSource.list();
}
});
},
select: function(){},
render: function(){}
});
Component.es6
$( ()=>
React.render(
<VideosTable/>,
$("#container")[0]
));
var VideosTable = React.createClass(
{
mixins: StateMixin,
render: function() {
var body = this.state.list.when({ //state is null here
pending: function(){
return <span className="ball"></span>;
},
failed: function(error){
return <div className="error">error.message</div>;
},
done: function(videos){
return <div>Videos</div>;
}
});
return <h2>hello</h2>;
}
});
Any idea what I'm doing wrong?
Edit: I've added a js bin thing here
http://jsbin.com/lekegicumo/2/edit?html,js,console,output
Looks like a typo in Mixin.es6 to me.
Change getState to getInitialState.
Also, in Component.es6:
Change mixins: StateMixin to mixins: [StateMixin].
The problem ended up being that the order of inclusion of JavaScript files was incorrect. Swapping some around fixed the issue.
are u using react v0.1.13.0
this is new way to initial your state using 'construct'
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html

Categories