Flux action not updating state, am I doing this wrong? - reactjs

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>

Related

Undefined props in componentDidMount

This is starting to get really frustrating. Basically, I cannot access props in my subcomponents. if I try to render them directly using this.props- it works, but if I need to do additional processes with them, or save them into state, I get undefined props all the time. I have a parent component, which looks something like this:
import React from 'react';
import Title from './EventSubComponents/Title';
import SessionInfo from './EventSubComponents/SessionInfo';
import SessionTime from './EventSubComponents/SessionTime';
import Location from './EventSubComponents/Location';
import Subscribers from './EventSubComponents/Subscribers';
class EventNode extends React.Component {
constructor(props) {
super(props);
this.state = {
'event': [],
}
}
componentDidMount() {
this.getEvent(this.props.location.selectedEventId);
}
getEvent(eventId) {
fetch('/api/v.1.0/event/' + eventId, {mode: 'no-cors'})
.then(function(response) {
if(!response.ok) {
console.log('Failed to get single event.');
return;
}
return response.json();
})
.then((data) => {
if (!data) {
return;
}
this.setState({
'event': data
})
});
}
render() {
return(
<div className="event-wrapper">
<Title
title = { this.state.event.title }
date = { this.state.event.start }
/>
<SessionInfo
distance = { this.state.event.distance }
type = { this.state.event.type }
/>
<SessionTime
start = { this.state.event.start }
end = { this.state.event.end }
/>
<Location location = { this.state.event.start_location }/>
<Subscribers
subscribers = { this.state.event.subscribers }
eventId = { this.state.event._id }
/>
</div>
);
}
}
export default EventNode;
And my sub-component SessionTime, which looks like this:
import React from 'react';
import moment from 'moment';
class Title extends React.Component {
constructor(props) {
super(props);
this.state = {
'title': '',
'date': '',
}
}
componentDidMount() {
console.log(this.props.title);
console.log(this.props.date);
// undefined both props.
this.convertToTitleDate(this.props.date);
this.setState({
'title': this.props.title
})
}
convertToTitleDate(date) {
var newDate = moment(date).format('dddd, Do MMMM')
this.setState({
'date': newDate,
});
}
render() {
return (
<div className="event-title-wrapper">
<h1> { this.state.title } </h1>
<div className="event-title-date"> { this.state.date } </div>
</div>
);
}
}
export default Title;
Could anyone explain, why both this.props.date and this.props.title are undefined in my componentDidMount function? I have couple more components in my EventNode and I have the same problems in them as well.
Changing componentDidMount to componentWillMount does not help. I am fairly certain I have problems in my parent EventNode component, but I cannot figure out where. Inside EventNode render() all the state variables are defined.
You initialize event to an empty array and pass down this.state.event.start and this.state.event.end to SessionTime, which will both be undefined on first render since event has not been loaded yet and there are no start and end properties on the array.
You could instead e.g. set event to null initially, and return null from the render method until the event has been loaded.
Example
class EventNode extends React.Component {
state = {
event: null
};
// ...
render() {
const { event } = this.state;
if (event === null) {
return null;
}
return (
<div className="event-wrapper">
<Title title={event.title} date={event.start} />
<SessionInfo distance={event.distance} type={event.type} />
<SessionTime start={event.start} end={event.end} />
<Location location={event.start_location} />
<Subscribers
subscribers={event.subscribers}
eventId={this.state.event._id}
/>
</div>
);
}
}

React—Change a components state on window.history.popstate

I have a React component that pushes song IDs to the url when the state changes. My problem is that when a user clicks 'back' on their browser, I need to change the state of my SongApp component. How do I do this?
class SongApp extends React.Component {
constructor(props) {
super(props);
this.state = {
song: props.songId
}
this.setSong = this.setSong.bind(this);
}
setSong(e) {
var songId = e.target.id;
this.setState({song: songId})
window.history.pushState({song: songId}, '', '?s='+songId)
}
render() {
var id = this.state.song;
var content = id ? <SongDisplay lyrics={ this.props.songData[id].lyrics } /> : <SongIndex songData={this.props.songData} setSong={this.setSong}/>
return(
<div className="song-app">
{content}
</div>
)
}
}
window.addEventListener('popstate', function(event) {
console.log('popstate fired!');
debugger;
if(event.state.song) {
// change SongApp state
}
});
I found out you can attach the component's method to a listener:
componentDidMount() {
window.addEventListener("popstate", this.setSongFromHistory);
}
setSongFromHistory(e) {
if(e.state.song){
e.preventDefault(); // stop request to server for new html
e.stopPropagation();
this.setState({song: e.state.song});
$('html,body').scrollTop(0);
}
}

callback function is not being called on event fire

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

React/Flux store doesn't change it's state

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;

stopping a timeout in reactjs?

Is there a way I can kill/(get rid of) a timeout in reactjs?
setTimeout(function() {
//do something
}.bind(this), 3000);
Upon some sort of click or action, I want to be able to completely stop and end the timeout. Is there a way to do this? thanks.
Assuming this is happening inside a component, store the timeout id so it can be cancelled later. Otherwise, you'll need to store the id somewhere else it can be accessed from later, like an external store object.
this.timeout = setTimeout(function() {
// Do something
this.timeout = null
}.bind(this), 3000)
// ...elsewhere...
if (this.timeout) {
clearTimeout(this.timeout)
this.timeout = null
}
You'll probably also want to make sure any pending timeout gets cancelled in componentWillUnmount() too:
componentWillUnmount: function() {
if (this.timeout) {
clearTimeout(this.timeout)
}
}
If you have some UI which depends on whether or not a timeout is pending, you'll want to store the id in the appropriate component's state instead.
Since React mixins are now deprecated, here's an example of a higher order component that wraps another component to give the same functionality as described in the accepted answer. It neatly cleans up any remaining timeouts on unmount, and gives the child component an API to manage this via props.
This uses ES6 classes and component composition which is the recommended way to replace mixins in 2017.
In Timeout.jsx
import React, { Component } from 'react';
const Timeout = Composition => class _Timeout extends Component {
constructor(props) {
super(props);
}
componentWillMount () {
this.timeouts = [];
}
setTimeout () {
this.timeouts.push(setTimeout.apply(null, arguments));
}
clearTimeouts () {
this.timeouts.forEach(clearTimeout);
}
componentWillUnmount () {
this.clearTimeouts();
}
render () {
const { timeouts, setTimeout, clearTimeouts } = this;
return <Composition
timeouts={timeouts}
setTimeout={setTimeout}
clearTimeouts={clearTimeouts}
{ ...this.props } />
}
}
export default Timeout;
In MyComponent.jsx
import React, { Component } from 'react';
import Timeout from './Timeout';
class MyComponent extends Component {
constructor(props) {
super(props)
}
componentDidMount () {
// You can access methods of Timeout as they
// were passed down as props.
this.props.setTimeout(() => {
console.log("Hey! I'm timing out!")
}, 1000)
}
render () {
return <span>Hello, world!</span>
}
}
// Pass your component to Timeout to create the magic.
export default Timeout(MyComponent);
You should use mixins:
// file: mixins/settimeout.js:
var SetTimeoutMixin = {
componentWillMount: function() {
this.timeouts = [];
},
setTimeout: function() {
this.timeouts.push(setTimeout.apply(null, arguments));
},
clearTimeouts: function() {
this.timeouts.forEach(clearTimeout);
},
componentWillUnmount: function() {
this.clearTimeouts();
}
};
export default SetTimeoutMixin;
...and in your component:
// sampleComponent.js:
import SetTimeoutMixin from 'mixins/settimeout';
var SampleComponent = React.createClass({
//mixins:
mixins: [SetTimeoutMixin],
// sample usage
componentWillReceiveProps: function(newProps) {
if (newProps.myValue != this.props.myValue) {
this.clearTimeouts();
this.setTimeout(function(){ console.log('do something'); }, 2000);
}
},
}
export default SampleComponent;
More info: https://facebook.github.io/react/docs/reusable-components.html
I stopped a setTimeout in my react app with Javascript only:
(my use case was to auto-save only after a clear 3 seconds of no keystrokes)
timeout;
handleUpdate(input:any) {
this.setState({ title: input.value }, () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => this.saveChanges(), 3000);
});
}

Resources