I have this very simple React
app:
https://codesandbox.io/s/24oq248v4n
which is about Oranges
and Lemons
, just that.
It basically gets the Oranges
or Lemons
from an (simulated) external API, depending on the pressed button.
Below you have the code for the Oranges
store (the one for Lemons
is quite similar).
src\resources\assets\js\stores\OrangeStore.js
import { EventEmitter } from "events";
import { sprintf } from "sprintf-js";
import AppDispatcher from "../dispatcher/AppDispatcher";
import AppApi from "../utils/AppApi";
const CHANGE_EVENT = "change";
let _response = null;
class OrangeStore extends EventEmitter {
constructor() {
super();
this.dispatchToken = AppDispatcher.register(this.handleActions.bind(this));
}
emitChange() {
this.emit(CHANGE_EVENT);
}
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
fetchOranges(data) {
AppApi.fetchOranges(data);
}
setOrangeResponse(data) {
_response = data;
}
getOrangeResponse() {
return _response;
}
clearOrangeResponse() {
_response = null;
}
handleActions(action) {
let nameObjectClass = this.constructor.name;
switch (action.type) {
case "FETCH_ORANGES":
this.fetchOranges(action.value);
break;
case "SET_ORANGE_RESPONSE":
this.setOrangeResponse(action.value);
break;
default:
console.error(sprintf('ATTENTION: action: "%s" entered on the wrong handle, the one for: "%s".', action.type, nameObjectClass));
break;
}
this.emitChange();
}
}
export default new OrangeStore();
Here you have the code that takes care of dispatching the actions:
src\resources\assets\js\actions\AppActions.js
import AppDispatcher from "../dispatcher/AppDispatcher";
class AppActions {
fetchOranges(data) {
AppDispatcher.dispatch({
type: "FETCH_ORANGES",
value: data
});
}
setOrangeResponse(data) {
AppDispatcher.dispatch({
type: "SET_ORANGE_RESPONSE",
value: data
});
}
fetchLemons(data) {
AppDispatcher.dispatch({
type: "FETCH_LEMONS",
value: data
});
}
setLemonResponse(data) {
AppDispatcher.dispatch({
type: "SET_LEMON_RESPONSE",
value: data
});
}
}
export default new AppActions();
Here you have the main code:
src\index.js
import React from "react";
import ReactDOM from "react-dom";
import AppActions from "./resources/assets/js/actions/AppActions";
import OrangeStore from "./resources/assets/js/stores/OrangeStore";
import LemonStore from "./resources/assets/js/stores/LemonStore";
import "./styles.css";
class App extends React.Component {
client = {
firstName: 'George',
lastName: 'Washington',
};
componentWillMount() {
OrangeStore.addChangeListener(this.handleOrangeResponse);
LemonStore.addChangeListener(this.handleLemonResponse);
}
componentWillUnmount() {
OrangeStore.removeChangeListener(this.handleOrangeResponse);
LemonStore.removeChangeListener(this.handleLemonResponse);
}
getOranges = () => {
AppActions.fetchOranges(this.client);
};
getLemons = () => {
AppActions.fetchLemons(this.client);
};
handleOrangeResponse = () => {
let response = OrangeStore.getOrangeResponse();
console.log('inside: index.js / handleOrangeResponse() {...} | where: response == ', response);
}
handleLemonResponse = () => {
let response = LemonStore.getLemonResponse();
console.log('inside: index.js / handleLemonResponse() {...} | where: response == ', response);
}
render() {
return (
<div className="App">
<h1>Oranges and Lemons</h1>
<h2>Yet another Flux test!</h2>
<button onClick={this.getOranges}>Get Oranges</button>
<button onClick={this.getLemons}>Get Lemons</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The code works almost fine but...
My problem is: the actions corresponding to the Oranges
are getting captured by the Lemons
listener and vice versa.
As you can see on the following image:
You are invited to try it by yourself on: https://codesandbox.io/s/24oq248v4n
To see the results: Info
and Errors
you have to open the Code Sandbox
console on the bottom.
In my opinion the listeners for Oranges
should capture only the Oranges
actions and the same for: Lemons
.
I believe I'm ommiting one key detail here.
Do you have any idea about this behavior?
If possible, please, fork my code above and provide the link of your fixed code here.
Some explanation is also welcome.
Thanks!
Answering my own question:
The problem was on the store files (both of them), where I had the following code:
src\resources\assets\js\stores\OrangeStore.js (the same applies to the other store)
class OrangeStore extends EventEmitter {
...
handleActions(action) {
let nameObjectClass = this.constructor.name;
switch (action.type) {
case "FETCH_ORANGES":
this.fetchOranges(action.value);
break;
case "SET_ORANGE_RESPONSE":
this.setOrangeResponse(action.value);
break;
default:
console.error(sprintf('ATTENTION: action: "%s" entered on the wrong handle, the one for: "%s".', action.type, nameObjectClass));
break;
}
this.emitChange();
}
...
}
Here I was doing one mistake, and it was I called: this.emitChange();
for all kind of actions, including those that are not related with the corresponding store.
Here was the the solution.
src\resources\assets\js\stores\OrangeStore.js (the same applies to the other store)
class OrangeStore extends EventEmitter {
...
handleActions(action) {
switch (action.type) {
case "FETCH_LEMONS":
this.fetchLemons(action.value);
// this.emitChange(); // THIS IS NOT NECESSARY HERE
break;
case "SET_LEMON_RESPONSE":
this.setLemonResponse(action.value);
this.emitChange();
break;
}
}
...
}
Also notice that it is not necessary to call: this.emitChange()
for every action, because if we do that, then the Flux
mechanism will be calling the event handlers (or callbacks) unnecessary, and if we have some kind of preprocessing on those handlers that is always done before doing its main functionality, then we are going to execute that innecessary.
When we are dealing with multiple stores, if we don't have this in mind we are going to run into issues.
Here is the code fixed: https://codesandbox.io/s/0pml774y9l
Here you have a quick preview with the main changes:
Interesting paragraph here:
https://scotch.io/tutorials/getting-to-know-flux-the-react-js-architecture
The Dispatcher is basically the manager of this entire process. It is the central hub for your application. The dispatcher receives actions and dispatches the actions and data to registered callbacks.
So it's essentially pub/sub?
Not exactly. The dispatcher broadcasts the payload to ALL of its registered callbacks, and includes functionality that allows you to invoke the callbacks in a specific order, even waiting for updates before proceeding. There is only ever one dispatcher, and it acts as the central hub within your application.
Also, here there are two examples of Flux
apps:
https://scotch.io/tutorials/build-a-react-flux-app-with-user-authentication
https://www.3pillarglobal.com/insights/getting-started-flux-react
Pay attention where they call: .emitChange()
.
Thanks!