From 2 weeks ago I'm facing a problem in my React/Flux app. It's done in ES6 and using webpack and babel.
It actually doesn't go inside the _onChange method ones the store emit the change event. So the component itself doesn't render again with the modified state.
Here you can take a look to my component:
import React from 'react';
import Item from 'components/item/item';
import Actions from './item-list.actions';
import Store from './item-list.store';
const StoreInstance = new Store();
class ItemList extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
this.state = this.getItemListState();
}
componentWillMount() {
StoreInstance.addChangeListener(this._onChange);
Actions.requestFlats(Actions.setFlats);
}
componentWillUnmount() {
StoreInstance.removeChangeListener(this._onChange);
}
_onChange() {
this.setState(this.getItemListState);
}
getItemListState() {
return {
flats: StoreInstance.getFlats()
}
}
render() {
return(
<ul className="item__list">{
this.state.flats.map((flat, index) => {
<li className="col-xs-12 col-sm-12 col-md-6 col-lg-6">
<Item key={index} flat={flat}></Item>
</li>
})
}</ul>
);
}
}
export default ItemList;
My actions:
import AppDispatcher from 'services/dispacher/dispacher';
import Constants from './item-list.constants';
let ItemListActions = {
getFlats: () => {
AppDispatcher.handleAction({
type: Constants.GET_FLATS,
data: {}
});
},
setFlats: (flats) => {
AppDispatcher.handleAction({
type: Constants.SET_FLATS,
data: {
flats
}
});
},
requestFlats: (callback) => {
AppDispatcher.handleAction({
type: Constants.REQUEST_FLATS,
data: {
callback
}
});
}
};
export default ItemListActions;
And store:
import AppDispatcher from 'services/dispacher/dispacher';
import AppStore from 'services/store/store';
import Api from './item-list.api';
import Constants from './item-list.constants';
class ItemListStore extends AppStore {
constructor() {
super();
this.flats = [];
}
requestFlats(callback) {
Api.getFlats(callback);
}
getFlats() {
return this.flats;
}
setFlats(flats) {
this.flats = flats;
}
}
const ItemListStoreInstance = new ItemListStore();
AppDispatcher.register((payload) => {
let action = payload.action;
switch (action.type) {
case Constants.GET_FLATS:
ItemListStoreInstance.getFlats(action.data);
break;
case Constants.SET_FLATS:
ItemListStoreInstance.setFlats(action.data.flats);
break;
case Constants.REQUEST_FLATS:
ItemListStoreInstance.requestFlats(action.data.callback);
break;
default:
return true;
}
ItemListStoreInstance.emitChange();
});
export default ItemListStore;
which extends of AppStore
import EventEmitter from 'events';
const CHANGE_EVENT = 'change';
class Store extends EventEmitter {
constructor() {
super();
}
emitChange() {
this.emit(CHANGE_EVENT);
}
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
}
Store.dispatchToken = null;
export default Store;
I have check this code many times and looking at examples over the whole Internet and I got no success.
I's supposed that when I do:
StoreInstance.addChangeListener(this._onChange);
the store will listen for my change event, but looks like it doesn't.
When I got the new data from the API, I execute setFlats and _onChange is not executed, so no changes on the UI are shown.
Do you see any issue in this code? Anything that could help me to solve it?
Thanks in advance.
I don't see any usage of you ItemListStore anywhere. Your component is using the "Store" class, which only extends EventEmitter. The connection to the ItemListStore is nowhere to be found.
This line (In your ItemListStore):
ItemListStoreInstance.emitChange();
will not trigger the emitChange() method in your Store.
The problem was actually in the store which was returning the ItemListStore instead of an instance of ItemListStore and then in the component I was having another instance, that's why it wasn't communication with each other.
Here is the fixed code for the ItemListStore:
import AppDispatcher from 'services/dispacher/dispacher';
import AppStore from 'services/store/store';
import Api from './item-list.api';
import Constants from './item-list.constants';
class ItemListStore extends AppStore {
constructor() {
super();
this.flats = [];
}
requestFlats(callback) {
Api.getFlats(callback);
}
getFlats() {
return this.flats;
}
setFlats(flats) {
this.flats = flats;
}
}
const ItemListStoreInstance = new ItemListStore();
AppDispatcher.register((payload) => {
let action = payload.action;
switch (action.type) {
case Constants.GET_FLATS:
ItemListStoreInstance.getFlats(action.data);
break;
case Constants.SET_FLATS:
ItemListStoreInstance.setFlats(action.data.flats);
break;
case Constants.REQUEST_FLATS:
ItemListStoreInstance.requestFlats(action.data.callback);
break;
default:
return true;
}
ItemListStoreInstance.emitChange();
});
export default ItemListStoreInstance;
Related
I have a component which renders if its prop.paramA is different from paramA value stored in cookie.
Following is my attempt to move the cookie management logic into a separate component. The problem is that the question component gets rendered only once when showQuestion is false so I never get to see the question.
Currently, my code looks like below
// question.js
import React from 'react';
import ToggleDisplay from 'react-toggle-display';
import {withCookies, Cookies} from 'react-cookie';
class Question extends React.Component {
static get propTypes() {
return {
cookies: instanceOf(Cookies).isRequired
}
}
constructor(props) {
super(props);
this.state = {
paramA: props.paramA,
showQuestion: props.showQuestion
};
}
componentDidMount() {
if (this.state.showQuestion) {
// do some ajax calls etc.
}
}
render() {
return (
<div id="simplequestion">
<ToggleDisplay show={this.state.showQuestion}>
<div>What is your SO profile?</div>
</ToggleDisplay>
</div>
);
}
}
export default withCookies(Question);
I wrote following CookieManager component -
// cookiemanager.js
import React from 'react';
import {withCookies, Cookies} from 'react-cookie';
class CookieManager extends React.Component {
static get propTypes() {
return {
cookies: instanceOf(Cookies).isRequired
}
}
constructor(props) {
super(props);
let propA = props.cookies.get('propA');
if (!propA || propA !== props.propA) {
props.cookies.set('propA', props.propA, { path: '/', maxAge: 604800});
console.log('changed', propA, props.propA);
props.propAChanged(true);
} else if (propA === props.propA) {
console.log('unchanged', propA, props.propA);
props.propAChanged(false);
}
}
render() {
return <div id="cookieManager"></div>;
}
}
export default withCookies(CookieManager);
Following is the top level app
// app.js
import React from 'react';
import Question from './question';
import CookieManager from './cookiemanager';
import { CookiesProvider } from 'react-cookie';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
paramA: props.paramA,
showQuestion: false,
avoidUpdate: false
};
this.updateShowQuestion = this.updateShowQuestion.bind(this);
}
updateShowQuestion(x) {
if (this.state.avoidUpdate) {
return;
}
this.setState({
paramA: this.state.paramA,
showQuestion: x,
avoidUpdate: !this.state.avoidUpdate
});
}
componentWillUpdate(nextProps, nextState) {
if (!nextState.avoidUpdate && nextState.paramA === this.state.paramA) {
this.setState({
paramA: this.state.paramA,
showQuestion: this.state.showQuestion,
avoidUpdate: true
});
}
}
render() {
return (
<CookiesProvider>
<CookieManager paramA={this.state.paramA} ridChanged={this.updateShowQuestion}>
<Question
paramA={this.state.paramA}
showQuestion={this.state.showQuestion}/>
</CookieManager>
</CookiesProvider>
);
}
}
export default App;
You're reading from state here:
<ToggleDisplay show={this.state.showQuestion}>
But showQuestion is set only once, in the constructor:
this.state = {
paramA: props.paramA,
showQuestion: props.showQuestion
};
When this.props changes your component is rendered, but you're still reading from this.state which is not updated, so the visible output is the same.
I see two ways you can solve this:
Update this.state.showQuestion whenever the props change:
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.showQuestion !== prevState.showQuestion)
return { showQuestion };
}
In render(), read from this.props.showQuestion:
<ToggleDisplay show={this.props.showQuestion}>
This way may look easier but with the other you gain more fine-grained control over state changes.
Another thing you've missed is found here:
componentWillUpdate(nextProps, nextState) {
if (!nextState.avoidUpdate && nextState.paramA === this.state.paramA) {
this.setState({
paramA: this.state.paramA,
showQuestion: this.state.showQuestion, // this line
avoidUpdate: true
});
}
}
You're receiving new props, but you're setting the old state: showQuestion: this.state.showQuestion. Instead, set the new showQuestion value from the new props - nextProps, this way:
componentWillUpdate(nextProps, nextState) {
if (!nextState.avoidUpdate && nextState.paramA === this.state.paramA) {
this.setState({
paramA: this.state.paramA,
showQuestion: nextProps.showQuestion, // this line
avoidUpdate: true
});
}
}
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import React, {Component} from 'react';
import {check} from 'meteor/check';
export const Adressen = new Mongo.Collection('Phonebook');
if (Meteor.isServer) {
Meteor.publish('ArrayToExport', function(branches) {
check(branches, [Match.Any]);
if(branches.length > 10){
return this.ready()
};
return Adressen.find(
{branche: {$in: branches}}, {fields: {firmenname:1, plz:1}}
);
});
}
.
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
class ExportArray extends Component{
constructor(props){
super(props);
this.state = {
branches: this.props.filteredBranches
};
}
render(){
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
this.props.filteredBranche is a pure array,generated through controlled input field. this.props.filteredBranches changes as Input changes, in parent Component.
I thought I was sending my this.props.filteredBranches as an argument through withTracker function. But nothing is passed to the publish function.
if (Meteor.isServer) {
arrayExfct = function (array){
return {
find: {branche:{$in: array }},
fields: {firmenname:1, plz:1}
};
}
Meteor.publish('ArrayToExport', function (array) {
return Adressen.find(
arrayExfct(array).find, arrayExfct(array).fields);
});
}
.
export default withTracker( () => {
arrayExfct = function(array) {
return {
find: {branche: {$in: array}},
fields: {firmenname:1, plz:1}
}
}
var array = ['10555'];
Meteor.subscribe('ArrayToExport', array );
var arrayExfct = Adressen.find(arrayExfct(array).find, arrayExfct(array).fields);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
It would help if you also added an example of where you used this component and how you pass props to it, but I think I see your problem.
You expect the local state in your rendering component to get into the withTracker container, but that would be the other way around. When you make the withTracker container, you are really making another react component that renders your display component (ExportArray) and passes the data (ArrayToExport) down into it.
So, props go like this currently:
external render -> withTracker component -> ExportArray
What you need to do it to get the filteredBranches (which you pass from a parent component?) from the props argument in withTracker and pass that to the subscribtion,
class ExportArray extends Component{
exportArrays () {
const { ArrayToExport } = this.props;
}
render(){
const { ArrayToExport } = this.props;
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker(propsFromParent => {
const { filteredBranches } = propsFromParent;
Meteor.subscribe('ArrayToExport', filteredBranches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Hi the issue is with the code below. The parameter called branches is the props so branches.branches is the array you passed in.
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Try the following.
export default withTracker( ({branches}) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Notice all that changed was
(branches)
became
({branches})
I solved my problem with a combination of Session Variables and State.
//Client
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
import {Meteor} from 'meteor/meteor';
import { Session } from 'meteor/session';
class ExportArray extends Component{
constructor(){
super();
this.state = {
x: [],
y: []
};
this.exportArrays = this.exportArrays.bind(this);
}
exportArrays(e){
e.preventDefault();
this.setState({x: this.props.filteredBranches});
this.setState({y: this.props.filteredPostleitzahlen});
}
render(){
var selector = {branche: {$in: this.state.x},plz: {$in: this.state.y}};
Session.set('selector', selector);
return(
<div>
<button onClick={this.exportArrays}> Commit </button>
</div>
);
}
}
export default withTracker( () => {
const ArrayfürExport = Meteor.subscribe('ArrayToExport', Session.get('selector') );
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
//Server
Meteor.publish('ArrayToExport', function (selector) {
console.log('von mongodb', selector);
return Adressen.find(
selector
, {
fields: {firmenname:1, plz:1}
});
});
}
I am learning Flux in ReactJS. I have written a simple code in ReactJS using Flux pattern. In this code some records are being displayed and there is an option to add a new record. The problem is that when I fire an event i.e. when I click the add button, the callback function is not being called from this.on(CHANGE_EVENT, callback); and as a result of that, the new record is not being displayed on screen. Therefore please tell, why this callback function is not being called? And how to resolve this issue?
Store.js
import React from 'react';
import Dispatcher from './Dispatcher.js';
import { EventEmitter } from "events";
class Store extends EventEmitter {
constructor() {
super();
this.records = [
{
id: 1,
name: 'First Record'
},
{
id: 2,
name: 'Second Record'
},
{
id: 3,
name: 'Third Record'
},
{
id: 4,
name: 'Fourth Record'
},
{
id: 5,
name: 'Fifth Record'
}
]
};
createRecord(name, id) {
this.records.push({
id: id,
name: name
});
this.emit("change");
}
addChangeListener(CHANGE_EVENT, callback) {
this.on(CHANGE_EVENT, callback);
}
handleActions(action) {
switch(action.type) {
case "ADD_RECORD": {
this.createRecord(action.name, action.id);
break;
}
}
}
getRecords() {
return this.records;
}
};
const recordsStore = new Store();
Dispatcher.register(recordsStore.handleActions.bind(recordsStore));
export default Store;
View.jsx
import React from 'react';
import Store from './Store.js';
import {addRecord} from "./Action.js";
class View extends React.Component {
constructor() {
super();
this.Store = new Store();
this.state = {records: this.Store.getRecords()};
}
render() {
return (
<div className="container" style={{marginTop:'25px'}}>
<ul className="list-group">
<li style={{backgroundColor:'#696969', color:'#f5f5f5', textAlign:'center', padding:'5px', fontSize:'16px', borderRadius:'5px 5px 0px 0px'}}><b>Records</b></li>
{this.state.records.map((eachRecord,index) =>
<ListItem key={index} singleRecord={eachRecord} />
)}
</ul>
<input type="text" ref="input"/>
<button onClick={()=>addRecord(this.refs.input.value)}>Add</button>
</div>
);
}
componentWillMount() {
this.Store.addChangeListener("change", this.updateStore);
}
updateStore() {
this.setState({
records: this.Store.getRecords()
});
}
}
class ListItem extends React.Component {
render() {
return (
<li className="list-group-item" style={{cursor:'pointer'}}>
<b>{this.props.singleRecord.name}</b>
<button style={{float:'right'}}>Delete</button>
</li>
);
}
}
export default View;
This is how I would set up a flux store -- the key points are:
register the dispatch callback in the constructor
It makes it easier to have a single CHANGE_EVENT constant and hide it inside the store.
Typically you'll want your stores to only have one instance. So when you export it you would export default new Store(). That way all components will be able to use the same store.
Store
const CHANGE_EVENT = 'change';
class Store extends EventEmitter {
constructor() {
super();
Dispatcher.register(this.handleActions.bind(this));
}
createRecord(name, id) {
// code..
this.emitChange();
}
emitChange() {
this.emit(CHANGE_EVENT);
}
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
handleActions(action) {
switch (action.type) {
case 'ADD_RECORD': {
this.createRecord(action.name, action.id);
break;
}
}
}
getRecords() {
return this.records;
}
}
export default new Store();
--
In the view, you should bind your listeners in componentDidMount and remember to remove the listeners in componentWillUnmount.
View
import Store from './Store';
class View extends React.Component {
constructor() {
super();
this.state = {records: Store.getRecords()};
}
render() {
// code..
}
componentDidMount() {
Store.addChangeListener(this.updateStore);
}
componentWillUnmount() {
Store.removeChangeListener(this.updateStore);
}
// use this syntax to keep context or use .bind in the constructor
updateStore = () => {
this.setState({
records: Store.getRecords()
});
}
}
If have a list of users and each Entry has a button »EDIT«. If the user clicks on it the following happens:
request the server for the form
Add the component <UserEditForm /> to the entry, what expands the entry
This works fine except one thing: If one clicks further buttons each Instance of the form receives the data of the last user form requested. That is because I have only only one userform property in the state.
So to solve this I want to exchange userform to userforms which should/could be an Object like that:
userforms: {
<id of first instance>: { … }, //formdata
<id of second instance>: { … },
…
}
But since I am new to React/Redux I do not really know how to do that, or what the »right« approach, or best practice is, to actually do it.
My Idea is to create a higher Order Component like so:
import React from 'react';
import {connect} from 'react-redux';
import {uuid} from '../../helpers/uuid';
export const formdatainstance = (FormInstance) => {
let id = null;
class FormDataMapper extends React.Component {
constructor (props) {
super(props);
id = uuid();
}
render () {
//extract the formdata from the state
//using the id
return <FormInstance { ...this.props } />
}
}
const mapStateToProps = (state) => {
console.log(id); //is null for one run
return {
userforms: state.userforms
};
};
return connect(mapStateToProps)(FormDataMapper);
}
So in the List component I can:
import UserEditForm from './UserEditForm';
import {formdatainstance} from './formdatainstance';
const MappedUserEditForm = formdatainstance(UserEditForm);
class List extends React.Component {
render(){
return (
{users.map(user => {
//more stuff
<MappedUserEditForm />
//more stuff
})}
);
}
}
So my Question: Is this a good Idea? If yes what would be the proper way to do the cleanup, so when in the life cycle of the component should I delete the data from the state? Is there another way to do that, which is easier?
Thanks for Help!
Here's what you can do...
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
class UserEditForm extends Component {
...
render() {
return <form onSubmit={this.props.handleSubmit(this.props.onSubmit)}>
...form fields
</form>
}
}
const mapStateToProps = (state, ownProps) => {
return {
form: ownProps.formId
}
}
export default compose(
connect(mapStateToProps),
reduxForm({
//...other redux-form options
})
)(UserEditForm);
Your ListComponent
render() {
return <ul>
{this.props.users.map(user =>
<li key={user.id}>
...
<UserEditForm formId={'form-' + user.id} onSubmit={...} />
</li>
)}
</ul>
}
This allows you to have a dynamic form name.
Even if the answer of #jpdelatorre seems to be the best hit for me, since it also includes the link to redux-forms, what will probably help me a lot, I would like to post my working solution here, just in case somebody might find it useful. It just hit me over night, so needed to test if my thought were right, what I could finally proof.
I was not able to do the whole Mapping with a sole HOC and I needed to add/modify reducers too. Basically it works that way:
Data Mapping is done by ID,
the original action creators are wrapped, such that the used id is attached to the Object
the reducers are wrapped two and called by the »datamapped« reducer
So the code of the original reducers and action creators does not need to be changed, what makes the wrapping kind of easy to use. I first wanted to use uuid's which are created on the fly, but I discarded that, to make possible to save and restore the whole application state.
so the HOC code is that:
import React from 'react';
import {connect} from 'react-redux';
// The Component to wrap,
// all of its actions
// its default state
export const formdatainstance = (FormInstance, Actions, defaultState = {}) => {
const mapStateToProps = (state) => {
return {
mappedData: state.mappedData
};
};
class FormDataMapper extends React.Component {
static propTypes = {
id: React.PropTypes.string.isRequired
};
static contextTypes = {
store: React.PropTypes.object
};
//most of mapping happens here
render () {
//wrap the action creators
const actions = Object.keys(Actions).reduce((list, key) =>{
list[key] = (...args) => {
const action = Actions[key](...args);
//handle asyn operations as well
if('then' in action && typeof action['then'] == 'function') {
action.then(data => {
//attaching the id
this.props.dispatch({...data, id: this.props.id});
});
} else {
//attach the id
this.context.store.dispatch({...action, id: this.props.id });
}
};
return list;
}, {}),
//there wont be any data at first, so the default state is handed
//over
mappedProps = this.props.mappedData.hasOwnProperty(this.props.id) ?
this.props.mappedData[this.props.id] : defaultState;
//merge the hotchpotch
let props = Object.assign({}, mappedProps, this.props, actions);
//clean up
delete props.id;
delete props.mappedData;
return <FormInstance { ...props } />
}
}
return connect(mapStateToProps)(FormDataMapper);
};
the reducer code:
//hlper method
export const createTypesToReducerMap = (types, reducer) => {
return Object.keys(types).reduce((map, key) => {
map[types[key]] = reducer;
return map;
}, {});
}
export const createMappedReducer = (reducerMap, defaultState = {}) => {
const HANDLERS = reducerMap.reduce((handlers, typeMap) => {
return { ...handlers, ...typeMap };
},{});
return (state, action) => {
if (!action.hasOwnProperty('id')) {
if (state === undefined) return defaultState;
return state;
}
const reducer = HANDLERS.hasOwnProperty(action.type) ?
HANDLERS[action.type] : null;
let a = {...action};
delete a.id;
return reducer !== null ?
Object.assign({}, state, { [action.id]: reducer(state[action.id], a)}) :
state;
}
}
and finally the store:
const userEditTypeReducerMap = createTypesToReducerMap(userEditTypes, userFormReducer);
const reducer = combineReducers({
…
mappedData: createMappedReducer(
[userEditTypeReducerMap], {})
…
});
export default compose(
applyMiddleware(
thunk
)
)(createStore)(reducer, {});
I have a Flux problem that's been killing me. I'm calling an action on page load, but for some reason it doesn't update the state in the component. In this example, I have this.props.count set to 5 (the default in TestStore). I then call an action to increase it in componentDidmount to 6, but it doesn't update the component's state. It stays at 5. Then if I click the link to manually update it, it goes from 5 to 7.
I think it has something to do with the Flux changeListener being added to the top-level component after the action is dispatched?
If I put the changeListener in componentWillMount instead of componentDidMount in the top-level component, then everything works. But that doesn't seem like the proper way? I feel like I'm missing something.
Here's a console.log and the components...
< Tester />
import React from 'react';
import TestActions from '../actions/TestActions';
export default class Tester extends React.Component {
componentDidMount() {
// this.props.count defaults to 5
// This brings it to 6
TestActions.increaseCount();
}
render() {
return (
<div>
// Count should display 6, but shows 5
Count: {this.props.count}
<br />
<a href="#" onClick={this._handleClick}>Increase</a>
</div>
);
}
_handleClick(e) {
e.preventDefault();
TestActions.increaseCount();
}
}
< Application />
import React from 'react';
import {RouteHandler} from 'react-router';
import TestStore from '../stores/TestStore';
export default class Application extends React.Component {
constructor() {
super();
this._onChange = this._onChange.bind(this);
this.state = this.getStateFromStores();
}
getStateFromStores() {
return {
count: TestStore.getCount()
};
}
componentDidMount() {
TestStore.addChangeListener(this._onChange);
}
_onChange() {
this.setState(this.getStateFromStores());
}
componentWillUnmount() {
TestStore.removeChangeListener(this._onChange);
}
render() {
return (
<RouteHandler {...this.state} {...this.props}/>
);
}
}
TestStore
var AppDispatcher = require('../dispatchers/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TestConstants = require('../constants/TestConstants');
var assign = require('object-assign');
var CHANGE_EVENT = 'change';
var _count = 5;
function increaseCount() {
_count = _count + 1;
}
var TestStore = assign({}, EventEmitter.prototype, {
getCount: function() {
return _count;
},
emitChange: function() {
console.log('TestStore.emitChange');
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
console.log('TestStore.addChangeListener');
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
AppDispatcher.register(function(action) {
var text;
switch(action.actionType) {
case TestConstants.INCREASE_COUNT:
increaseCount();
TestStore.emitChange();
break;
default:
// no op
}
});
module.exports = TestStore;
As you said, the issue is in <Application />: You start listening to the store in componentDidMount, whereas you should do that in componentWillMount, otherwise you start listening to changes after all the components are mounted, therefore you lose the initial increment.
componentWillMount() {
TestStore.addChangeListener(this._onChange);
}
Anyway, I would suggest to perform the action in the top component:
In <Application />
componentDidMount() {
TestActions.increaseCount();
},
_handleClick() {
TestActions.increaseCount();
},
render() {
return <Tester callback={this._handleClick} count={this.state.count} />
}
In <Tester/>
<a href="#" onClick={this.props.callback}>Increase</a>