Avoiding event chains with asynchronous data dependencies - reactjs

The Facebook Flux dispatcher explicitly prohibits ActionCreators from dispatching other ActionCreators. This restriciton is probably a good idea since it prevents your application from creating event chains.
This however becomes an issue as soon as you have Stores containing data from asynchronous ActionCreators that depend on each other. If CategoryProductsStore depends on CategoryStore there doesn't seem to be a way to avoid event chains when without resorting to deferring the follow-up action.
Scenario 1:
A store containing a list of products in a category needs to know from which category ID it should fetch products from.
var CategoryProductActions = {
get: function(categoryId) {
Dispatcher.handleViewAction({
type: ActionTypes.LOAD_CATEGORY_PRODUCTS,
categoryId: categoryId
})
ProductAPIUtils
.getByCategoryId(categoryId)
.then(CategoryProductActions.getComplete)
},
getComplete: function(products) {
Dispatcher.handleServerAction({
type: ActionTypes.LOAD_CATEGORY_PRODUCTS_COMPLETE,
products: products
})
}
}
CategoryStore.dispatchToken = Dispatcher.register(function(payload) {
var action = payload.action
switch (action.type) {
case ActionTypes.LOAD_CATEGORIES_COMPLETE:
var category = action.categories[0]
// Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown.
CategoryProductActions.get(category.id)
...
Scenario 2:
Another scenario is when a child component is mounted as the result of a Store change and its componentWillMount/componentWillReceiveProps attempts to fetch data via an asynchronous ActionCreator:
var Categories = React.createClass({
componentWillMount() {
CategoryStore.addChangeListener(this.onStoreChange)
},
onStoreChange: function() {
this.setState({
category: CategoryStore.getCurrent()
})
},
render: function() {
var category = this.state.category
if (category) {
var products = <CategoryProducts categoryId={category.id} />
}
return (
<div>
{products}
</div>
)
}
})
var CategoryProducts = React.createClass({
componentWillMount: function() {
if (!CategoryProductStore.contains(this.props.categoryId)) {
// Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown.
CategoryProductActions.get(this.props.categoryId)
}
}
})
Are there ways to avoid this without resorting to defer?

Whenever you are retrieving the state of the application, you want to be retrieving that state directly from the Stores, with getter methods. Actions are objects that inform Stores. You could think of them as being like a request for a change in state. They should not return any data. They are not a mechanism by which you should be retrieving the application state, but rather merely changing it.
So in scenario 1, getCurrent(category.id) is something that should be defined on a Store.
In scenario 2, it sounds like you are running into an issue with the initialization of the Store's data. I usually handle this by (ideally) getting the data into the stores before rendering the root component. I do this in a bootstrapping module. Alternatively, if this absolutely needs to be async, you can create everything to work with a blank slate, and then re-render after the Stores respond to an INITIAL_LOAD action.

For scenario 1:
I would dispatch new the action from the view itself, so a new action -> dispatcher -> store -> view cycle will trigger.
I can imagine that your view needs to retrieve the category list and also it has to show, by default, the list of products of the first category.
So that view will react to changes con CategoryStore first. Once the category list is loaded, trigger the new Action to get the products of the first category.
Now, this is the tricky part. If you do that in the change listener of the view, you will get an invariant exception, so here you have to wait for the payload of the first action to be completely processed.
One way to solve this is to use timeout on the change listener of the view. Something similar to what is explained here:
https://groups.google.com/forum/#!topic/reactjs/1xR9esXX1X4 but instead of dispatching the action from the store, you would do it from the view.
function getCategoryProducts(id) {
setTimeout(() => {
if (!AppDispatcher.isDispatching()) {
CategoryProductActions.get(id);
} else {
getCategoryProducts(id);
}
}, 3);
}
I know, it is horrible, but at least you won't have stores chaining actions or domain logic leaking to action creators. With this approach, the actions are "requested" from the views that actually need them.
The other option, which I haven't tried honestly, is to listen for the DOM event once the component with the list of categories is populated. In that moment, you dispatch the new action which will trigger a new "Flux" chain. I actually think this one is neater, but as said, I haven't tried yet.

Related

Use useEffect or not

Is it bad practice to use useEffect?
And should useEffect be avoided if possible due to re-renders?
This question arised yesterday when a colleague asked for a code review and we had different opinions on how to solve this.
We are creating an app that shows some kind of documentation which could be sorted in chronological or reversed chronological order. This is decided by a button in the apps top bar with a default value of chronological order, this value is stored in a global redux state and will be used in every call to fetch documentation.
In this example we update sortOrder on button click and as an effect of that we fetch data.
If I understand this correctly, we render once when sortOrder state change, and once after data is fetched.
Pseudo code ish
interface AppState = {
sortOrder: SortOrder:
documentation: Documentation[];
}
reducer(){
case toggleSortOrder:
const order = state.sortOrder === 'asc' ? 'desc' : 'asc';
return {
....state,
sortOrder: order;
}
}
const AppBar = () => {
const dispatch = useDispatch();
return <div><button onClick={dispatch(toggleSortOrder)}>Change sort order</button>
</div>;
}
const DocumentationList = (type: DocumentationType) => {
const dispatch = useDispatch();
const sortOrder = useSelector((state) => state.appState.sortOrder);
const documentation = useSelector((state) => state.appState.documentation);
useEffect(() => {
// action is caught by redux-saga and a call to docApi is made through axios
dispatch(getDocumentation.request(type, sortOrder)
},[sortOrder]);
return documentation.map((doc) => <Documentation data={doc} />);
}
Is this bad practice?
Should we avoid useEffect and fetch data on click and update sortOrder in saga instead?
Reading docs and blogs I mostly see examples of how en when to use them.
In my opinion, I would go with solution more-less like yours, with splitting responsibilities between element which externally changes query params, and element which is displaying data based on current query params. If you decide to put all logic in button click handler then you are kinda coupling too much list and button, because in order to delegate all work to button click you must dig into DocumentationList fetch-data implementation and to copy it to another place(button related saga or in button click handler) in order to fetch data from another place in the app, and not just from the DocumentationList itslef.
From my perspective, only DocumentationList should be responsible to fetch data, and noone else. But you should provide way to subscribe, from documentation list, to some external query params(sort, filters etc) and when they change(if they exist) data should be loaded.
Right now you only have sort, but in case when you can potentially have more params that can be externally modified, then I would dedicate more complex redux part to query params, something like documentationQueryParams: { sort: "asc", filters: { name: "doc a", type: "type b" } }, and then inside DocumentationList I would use custom hook, for example const queryParms = useDocumentationQueryParams(); which will return standardized query params, and in useEffect I would subscribe to those queryParms change - whenever they change I will easily fetch new data since you know what is the structure of the queryParms(they must be standardized is some way). Like this you coupled them but in very flexible way, whenever you need new param you will update only filter/query-related component, because in DocumentationList you relay on standardized hook output and you can easily create generic mechanism to output query string, or body data, in order to make new request and to fetch new data.
In terms of performance, there is really no difference between hooks-based approach and moving all to click handlers, because in DocumentationList your render part should rerender only when list change, no matter how list is being changed.

React with Redux Update only state

I'm working on integrating Redux in an already finished SPA with ReactJS.
On my HomePage I have a list of the 4 newest collections added which on render, I fetch with axios from my database. These are then saved in Redux Store and displayed on the React UI.
My mapStateToProps look something like this:
const mapStateToProps = (state) => ({
credentials: credentials(state),
collections: collections(state)
});
Where credentials is irrelevant and collections is:
const collections = (state) => {
if (state.collectionsHomeViewReducer.fetching === true) {
return {
fetchingCollections: true
}
}
else if (state.collectionsHomeViewReducer.data) {
const response = state.collectionsHomeViewReducer.data;
return {
collections: response.collections,
fetchedCollections: true,
fetchingCollections: false
}
}
else if (state.collectionsHomeViewReducer.fetched === false) {
return {
fetchedCollections: false,
fetchingCollections: false
}
}
};
What is it I want to do:
Update the store state every time another client, or the current client, adds a new collection. Moreover, I do not wish for the UI to update immediately after I dispatch(action), I want it to update when a user refreshes the page or when he navigates to another view and returns ( I believe what I'm trying to say is when componentDidMount is called ).
What have I achieved so far:
By using socket.io, I
socket.emit("updateCollectionsStore")
socket.on("updateCollectionsStore")
and
socket.broadcast.emit("updateCollectionsStore")
in their respective places in the application. The final call of
socket.on("updateCollectionsStore")
after the broadcast, is in the main file of the page, app.jsx where the store is also located. The function there looks like this:
socket.on("updateCollectionsStore", () => {
store.dispatch(getCollectionsHomeView());
});
The store is updated and everything works fine, as viewed from the Redux Dev Tools.
What I can't seem to figure out is to tell the props not to change due to the fact that mapStateToProps is called every time an action is dispatched.
Why do I need this: The HomePage can deal with a continuous UI update and data fetching but I also have a page ReadAllPage where you can real all collections. The problem is if there will always be the newest post on the top, whenever a new one is added, the current one is pushed downwards. In case somebody had the intent to click the one that was pushed down, now he might have accidentally clicked the one that took its place, which is not wanted.
What else should I do different or further to achieve the result I want?
Thank you.
According to your needs, I would have two properties in the state. First is that is currently visible on the HomeView and the second is that is updated via sockets. Once a user navigates to the HomeView you can just replace the first collection with the second one.

Update value of variable from Firebase query?

I currently am trying to pull the value of an item from a firebase using once, and use this value to populate the var itemsList. However, although itemsList is correctly populated within the once call, it is logging as undefined outside the call. I think this may have something to do with the asynchronous nature of Firebase, but I'm not sure how to remedy this problem. Any suggestions?
submitAnswer: function() {
var userRef = usersRef.child(key);
//get items_list for user
var itemsList;
userRef.once('value', (snap) => {
itemsList = snap.val().items_list;
console.log('items list populated here' + itemsList);
});
console.log("items list not populated here" + itemsList);
},
You're correct about the Firebase call, it's asynchronous and the code isn't going to wait for it to complete before logging the unpopulated itemsList.
If you're only looking to do something simple with the data, just be sure to check that it exists before performing any action with it (and handle it like you would any async data).
if(itemsList){
console.log('My items have arrived! ' + itemsList);
}
If that data is going to be propagated further down your app it is usually suggested to make a call to setState() with your response data from Firebase to trigger a re-render of your components with the new data you just fetched.
So something along the lines of:
userRef.once("value", (snap) => {
itemsList = snap.val().items_list;
this.setState({
items: itemsList;
});
}.bind(this));

React.js + Immutable.js: best practices for shouldComponentUpdate with filtered lists

What are the best practices for filtering a Immutable.List for passing down to child components as regards to shouldComponentUpdate?
Illustrative example: I'm building a calendar. It has a <Week> component, holding seven <Day> components. The <Week> receives a list of all calendar events, then renders the <Day>s and passes a filtered subset of events to each of them as props.
All the <Day> components have PureRenderMixin attached to them, with the intent of preventing useless re-renders. But, given that Immutable.List.filter returns a new object each time, when a single event is added, every <Day> component will receive a 'new' event list as prop and re-render, even when all but one of them have the same content.
My current idea is writing a custom shouldComponentUpdate that compares hashCodes of every list component decides to re-render based on that. Is there a better, more established way to do it?
Code example as requested. Assume <Week> is wired up to a flux store and gets the events from it.
Day = React.createClass({
propTypes: { events: React.PropTypes.instanceOf(Immutable.List) },
mixins: [ React.addons.PureRenderMixin ],
render: function(){
const events = this.props.events.map((event) => {
return <div key={event.get('id')}>{event.get('name')}</div>
})
return <div>{events}</div>
}
})
Week = React.createClass({
propTypes: { events: React.PropTypes.instanceOf(Immutable.List) },
mixins: [ React.addons.PureRenderMixin ],
render: function(){
const days = [1,2,3,4,5,6,0].map((weekday) => {
const dayEvents = this.props.events.filter(event => event.get('weekday') === weekday)
return <Day events={dayEvents} key={weekday} />
})
return <div>{days}</div>
}
})
You should restructure the model like this-
{'Monday':{...events...}, 'Tuesday':{...events...}, ...}
This way you can pass the right set of events to the right child Day component.
Update
Noticed you are using numbers for weekdays, simply substitute day names with numbers, or use a List/array to store events. Basically we are doing away with the need of calling filter.

React Flux - Return value from flux dispatcher / store

I am using the flux-pattern and the flux dispatcher. I need to return a value from 'TextStore' to an action after creating a new TextItem because I need to reference it in another store.
Here is a very simple version of what I want to do:
// stores.ts
var TextStore = {
add(){
// here I want to return a new ID
return createRandomID();
}
...
}
var ModuleStore = {
labelTextID; // refers to `id` on Text
...
}
// moduleactions.ts
...
import PageActions from './PageActions';
var ModuleActions = {
add: function (module) {
var textID = PageActions.add(); // here I need to get the ID of the newly create `Text`
module.labelTextID = textID;
Dispatcher.dispatch({
action: 'ADD_MODULE',
module: module
})
},
...
}
Now when I add a new Module via dispatching an action, I want to create a new Text as well and return its newly created ID from the store before.
The most obvious way would be to require the TextStore inside ModuleActions and call add() directly. Is that against the flux-pattern?
Is there any way to accomplish that, maybe with promises? Sending callbacks via the dispatcher to the store doesnt work, because I cannot dispatch while another dispatch is unfinished.
Would be great if you guys can help me!
Calling the Store's method directly is an anti-pattern for Flux. If you directly call TextStore.add() then you are not following the
Action -> Dispatcher -> Store --> View --> Action cycle.
In flux, the data should always generate in the action. This makes more sense when the process of generation of data is aync. In your case you were able to get around this because generation of data is not async. You are directly generating the data in the TextStore and then worrying about how to return that value.
In your case generate the id in the action as you would have done if it was an async backend event, then pass that id down to the TextStore via dispatcher and after that also pass the same id to the ModuleStore via the dispatcher.
var ModuleActions = {
add: function (module) {
var textID = new Date().getTime(); // this is the CHANGE i added
PageActions.add(textID);
module.labelTextID = textID;
Dispatcher.dispatch({
action: 'ADD_MODULE',
module: module
})
}
}
You could also break this down into further smaller, more specific actions. I kept it as-is so I could highlight the one line you should change.
Is there any way to accomplish that, maybe with promises? Sending callbacks via the dispatcher to the store doesnt work, because I cannot dispatch while another dispatch is unfinished.
You can have your async call PageActions.add(); before you dispatch the ModuleActions.add and pass the returned value as a parameter to ModuleActions.add

Resources