Dispatching an action after Store state change in ReactJS (with Alt) - reactjs

My application is a simple file explorer, and has three components:
A Tree: representing a directory tree created by user;
A Simple page: with the selected directory content;
A Notification Component: to handle messages triggered by user actions.
When the user creates a new folder, the file store (containing all data related to both components) is updated with the server response and notifies the components, with is working fine.
But I need to trigger an Action that will add a message with the result to NotificationStore. This should be an easy task, but I'm trying to do this for a long time, with no success.
The closer I got to solve was creating a "store.listen()" inside the componentDidMount() and then calling my Action (adding the notification message), which was successfully triggered. But it result in an error: "Cannot dispatch in the middle of a dispatch".
Is there any way to call an action after store state change?
Update:
An alternative could be binding the NotificationStore with FileActions and then use the "waitFor(FileStore.dispatchToken)" and THEN set the message in the Store. It could work in this case, but maybe not in others.

In your store that need to listen to NotificationStore action, add an listener:
this.bindListeners({
onActionSomething: NotificationAction.sendNotifcation
});
this will take trigger function in this store called onActionSomething after action sendNotifcation in NotificationActions.
Do not forget to waitFor the dispatcher from other Notification:
onActionSomething(data) {
this.waitFor(NotificationStore);
this.pages = data;
}
Data parameter will be the same as in Notification one

Related

How to avoid letting an async thunk that is no longer useful (the app is not expecting it anymore) from being able to update the state?

I have the following pattern on my single page app (React + Redux).
It runs every time a load a page on the app. User navigates to a specific page, and the loadPageThunk is dispatched. The initial state of the page shows a spinner to the user. This is used for example, in a blogpost page.
That thunk will get some async data (the blogpost), and then will show the page with that data.
It works fine. When the user navigates away from the page. A useEffect dispatches a RESET action to reset the state back to its initial value.
My question is:
What if the async call takes too long to complete and the user navigates away? It will create a problem because now there's a pending promise that will complete in an unexpected time. How can I prevent that completion from updating my state?
Imagine the following steps for an async call that is taking 10 seconds to complete:
#### FIRST PAGE LOAD ####
USER VISITS blog/slug-1
loadPageThunk() IS DISPATCHED
blogPost1 STARTS GETTING FETCHED (WILL TAKE 10 SECONDS)
USER NAVIGATES AWAY
#### SECOND PAGE LOAD ####
USER VISITS blog/slug-2
blogPost2 STARTS GETTING FETCHED (WILL TAKE 10 SECONDS)
USER IS STILL SEEING SPINNER
blogPost1 (FROM THE PREVIOUS VISIT) HAS COMPLETE AND WILL UPDATE THE STATE
USER NOW SEES blog/slug-2 WITH THE DATA FROM blogPost1 WHICH IS AN ERROR
blogPost2 WILL EVENTUALLY COMPLETE AND USER WILL SEE A CONTENT FLICKER ON THE PAGE
QUESTION
How can I avoid pending promises that are no longer useful from being able to update the state?
This problem is not currently happening in my app, but I think that a good design should account for that.
Should I add an ID for my LOAD_PAGE cycle, so I can check the ID of the current cycle before allowing callbacks / async code from updating the state when IDs don't match? How do people usually handle this?
Personally I store blog data as entities (posts, comments, etc.) keyed by id and collections. The collection is just the array of post ids on a particular page.
For example,
{
entities: {
posts: {
1: {...},
2: {...}
},
comments: {
123: {...},
999: {...}
}
},
collections: {
"blog/slug-1": [99,98,97...],
"blog/slug-2": [89,88,87...],
}
}
This sort of structure means that every page can save its data in the correct place regardless of whether it is the current page or not. It also means that every page can select its own data and can see whether that data already exists in the state.
The promise returned by createAsyncThunk has an abort() method attached which can be used to 'cancel' the promise. See canceling while running. You can call abort() in your cleanup to prevent the thunk from being fulfilled.
In your reducers, if you are handling the rejected case for your thunk, then you can add an exception for cases where the error name is AbortError to do nothing instead.
To expand a bit about your specific situation: a good rule of thumb is that if you find yourself 'resetting' state when you unmount the component, then it should have just been local component state in the first place.

Storing state (loading, error ...) about a specific entity in a Redux store

TLDR; Individual entities in a store can be in different states, including loading and error states. How can we correctly reflect this in our UI with a "fire and forget" approach to Redux actions, while preferably keeping the behavior of our action creators consistent?
The convention is to "fire and forget" an action and subscribe to store updates, as opposed to dispatching an action and subsequently dealing with its return value or promise.
Several examples illustrate this:
// This is a web app that lets users create and book events
const loadEvents = () => dispatch => {
dispatch(loadEventsRequest());
return fetchFromApi('https://...')
.then(
json => dispatch(loadEventsSuccess(json)),
error => dispatch(loadEventsFailure(error))
)
}
componentWillMount() {
this.props.loadEvents(); // Fire it; the state will be updated eventually
}
render() {
return this.props.events.map((event) => ( <Event event={event} /> ));
}
The list of events can be in several states, e.g. a loading state. We can accommodate this by designing our state like this:
entities: {
events: {
1: {
title: "Movie night"
},
...
}
},
visibleEvents: [
isFetching: false,
ids: [1, 2, 3]
]
It's easy to show a loading indicator based on the value of visibleEvents.isFetching.
State for specific entities
Let's imagine that events can be booked, canceled and deleted. All of these actions may result in errors from the backend ("The event is fully booked") or success scenarios ("The event was booked"). We can notify the user in two ways:
Alternative 1)
Dispatch the action from the component and respond to it using then/catch. Caught an error? Display it. The state stays local.
For this to work our loadEvents() action creator would need to be changed. Currently it catches errors and fires loadEventsFailure(), so our component doesn't know whether the action failed or succeeded (can't be caught).
We could re-throw the error from the action creator or reject the promise, (or don't catch it at all), so that our component gets a chance to catch and respond to it. My biggest concern is that we end up with inconsistent behavior across our action creators -- some throw errors, some don't. We could make it a convention to always throw errors, but unless the component catches the them we end up with "Uncaught" errors all over the place.
It doesn't feel right to let the component decide the behavior of the action creator (whether to throw/reject), especially if other components want to use it as well. Also, there would be no use for a booking reducer in this case because our state never needs to be updated during the booking process.
Alternative 2)
Store every type of result (error or success state) in the Redux state together with their specific entities (i.e. each event can have multiple states related to booking, deletion and cancellation).
Our component wouldn't need to "respond" to the action creator; it could simply fire an action and read the result off the Redux state (pretty idomatic). Conceptually:
handleBookingButtonClicked() { this.props.bookEvent(id); }
// ...
render() { if (store.entities.events[id].bookingError) return (<div>Booking failed</div>); }
All components that relate to booking of an event can read bookingError. Components related to cancellations can read cancellationError, etc.
(Note: If it's tempting to only have one "global" error object in the store, or one error object per entity which contains any type of error related to it, this would quickly fail if you want to display several components simultaneously.)
A couple of issues with this approach:
Eventually the errors would need to be cleared; we don't want to display an error indefinitely.
There could potentially be a lot of different state indicators for each event: loading, booking, deletion, cancellation, updating etc. In addition, each of these indicators can be in different states: Booking an event? It could succeed, fail or be in progress. And if it fails, we want to know why.
Two different components that alllows booking would display the same error.
Which approach would you recommend, and how would you address the issues described (and the proposal to have consistent action creators)?

How to update a child component state after an ajax call

To learn react + flux, I am building a simple compose box where the paragraph is broken down into multiple tweets and then by clicking a button you can tweet the series of tweets. The high level app has the following components:
<div>
<ComposeBox tweetParaText={this.state.tweetParaText} ref='composeBox' />
<DisplayTweets tweetCollection={this.state.tweetCollection} ref='displayTweets' />
<TweetButton signedInSignature={this.state.signedInSignature} ref='tweetButton' />
</div>
Once you are done composing the text, you click on the TweetButton. The TweetButton has 4 UI states:
Ready (Show the Tweet Button)
Tweeting (in which case I show a
loading gif as well as change the text from "Tweet" to "Tweeting..")
Tweeted Succesfully (In which case I want to revert text of the button to "Tweet", hide the ajax laoder gif and show a message "Tweets sent successfully!" for a duration of 7 seconds.)
Error (In which case I want to revert text of the button to "Tweet", hide the ajax loader gif and show an error message)
These states are unique to the tweet button and are different from the application state which I am storing in the store.
The onclick of the TweetButton component is as follows:
_onClick:function(){
this.setState({buttonState:tweeting});
ActionCreator.tweet(this.props.tweetCollection,this.props.signedInSignature);
}
On changing the state here, the UI changes for this state happen, as that is how the component has been written. However, I am a bit stumped after this.
The flow of data being unidirectional, the action upon completion dispatches an event to the store which updates its state and thus the state of the ComposeBox component. But how and where do I set the state of the TweetButton component? Should I write a different store for this? Or is there a better way of achieving this?
I use the same store for the status/state of the call.
Lets say the result of your call will be stored in _Tweets = {}; inside the TweetStore object.
I register the results uniquely (not mandatory I guess) inside the store like this
_Tweets["someresultid/tag"] = {
data: result.data,
status: result.status
}
TweetStore is listening for 2 events : case : "Loading" and case : "ReceiveResults/answer/wtv"
When the user initiate the call you'll dispatch "loading" at the same time than your call. Then when it receive the answer you dispatch "answer".
Your view is listening for any change of your store, when a change occurs the callback will check the status and rerender accordingly (lording,error or result).
I hope it helps

Accessing react-router from flummox action/store

I want to be able to make an API call in a Flummox action and transition differently depending on the response. I can pass the router into the action call but am looking for advice on a potentially better way.
UPDATE:
The correct answer is below but I wanted to add some detail to this.
I'm doing an isomorphic app that 1. needs to get data from an api and 2. may need to redirect based on the api response. Whatever I do needs to work through an express.js app and through react.
I made a small lib that does the api call and return some results. I pass it an object (query params object from express for the server-side or a similar object I create for the react-side). This lib makes the request, determines if a redirect is needed and passes back errors, path (string), redirect (boolean), and data (json).
In express, if the redirect boolean is true, I just redirect to it with the current query params. If it's false, I pass the data to flux through an action which updates a store. I then renderToString with react, serialize stores so the clint-side can bootstrap, and send a rendered page to the client.
In react, the redirect boolean isn't important, I get the response back from my lib, pass the data to my flux action, and just transition to whatever the path is. There's really no notion of redirection. Just go to the path no matter what.
Hopefully this is helpful to someone.
In my setup I have my own router module which just wraps the instance of react-router that I create at startup. That makes it easy for any part of the application to just require that module and do what it needs to.
But I would advise you not to have side effects like a call to the router inside your actions. Actions should concern themselves on mutating your application state, and nothing more. It should be possible to call the same action from anywhere in your application which needs to perform the mutation that the action encapsulates.
So if you're switching routes during an action, you're basically tying that action to that particular use case. Let's take an example. You have a todo list, with an input box at the bottom to add a new todo. For that use case, it might be useful to switch route after you saved the todo. Perhaps you switch to Recent Todos or something. But then a new use case comes along where you want to be able to add new todos during another workflow, perhaps the user is planning her week and should be able to add todos on different days. You want the same action that adds a todo, but you certainly don't want to switch routes because the user is still planning the week.
I haven't used Flummox a lot, but from my understanding your Flux object returns whatever the action returns when you trigger an action. So instead of putting the route change inside your action, make sure to return the response from the action and let your component decide if the route should be changed. Something like this:
// todo-action.js
class TodoActions extends Actions {
createMessage(todo) {
return TodoStore.saveToServer(todo);
}
}
// todo-list.js
const TodoList extends React.Component {
render() {
...
}
addTodo(todo) {
this.props.flux.addTodo(todo).then(response => {
if (response.some.prop === someValue) {
this.props.router.transitionTo(...);
}
});
}
}
That way, the action is still nicely decoupled from the route change. If you want to do the route switch in more than one place, you could encapsulate that in a addTodoAndSwitchRoute method in your Flux class.

Where should HTTP requests be initiated in Flux?

There is a plenty of discussion on how to communicate with external services in Flux.
It is pretty clear that the basic workflow is to fire an HTTP request, which will eventually dispatch successful or failure action based on the response. You can also optionally dispatch "in progress" action before making the request.
But what if request's parameters depend on store's state? Nobody seems to mention it.
So essentially, based on user interaction with the view, an ACTION is dispatched. Store owns logic on how to transition from current state0 to the next state1 given ACTION. Data from state1 is needed to properly form new HTTP request.
For example, user chooses a new filter on the page, and store decides to also reset pagination. This should lead to a new HTTP request with (new filter value, first page), not (new filter value, current page from state0).
View can not make the HTTP call itself right with user's interaction because it then would have to duplicate store's logic to transition to the next state.
View can not make the HTTP call in its store's onChange handler because at this point it is no longer known what was the origin of the state change.
It looks like a viable option to make store fire the HTTP request in the action handler, after it transitioned to the next state. But this will make this action implicitly initiating HTTP call, which disables neat possibility to have a replayable log of dispatched actions for debugging.
Where should HTTP requests be initiated in Flux?
Let's start at the bottom:
It looks like a viable option to make store fire the HTTP request in the action handler, after it transitioned to the next state. But this will make this action implicitly initiating HTTP call, which disables neat possibility to have a replayable log of dispatched actions for debugging.
This can be mitigated by not initiating HTTP requests if you're in debugging/replay mode. This works great as long as the only thing you do in your HTTP request handlers is fire actions (e.g. SUCCESS and FAILURE actions). You could implement this with a simple global boolean (if (!debug) { httpReq(...) }), but you could also make the pattern a bit more formal.
In Event Sourcing parlance, you use Gateways for such purposes. In normal operation, the Gateway makes your HTTP requests, and in debugging, you turn the Gateway off (so it doesn't make any HTTP requests).
That said, I think the problem can actually be solved by rethinking where your HTTP requests are made.
So essentially, based on user interaction with the view, an ACTION is dispatched. Store owns logic on how to transition from current state0 to the next state1 given ACTION. Data from state1 is needed to properly form new HTTP request.
In the second link in your question (Where should ajax request be made in Flux app?), I recommend doing your writes in action creators but reads in the stores. If you extrapolate that pattern into your use case, you might end up with something like this (pseudocode and long variable names for clarity):
class DataTable extends React.Component {
render() {
// Assuming that the store for the data table contains two sets of data:
// one for the filter selection and one for the pagination.
// I'll assume they're passed as props here; this also assumes that
// this component is somehow re-rendered when the store changes.
var filter = this.props.filter;
var start = this.props.start;
var end = this.props.end;
var data = this.props.dataTableStore.getDataForPageAndFilter(
start, end, filter
);
// the store will either give us the LOADING_TOKEN,
// which indicates that the data is still loading,
// or it will give us the loaded data
if (data === DataTableStore.LOADING_TOKEN) {
return this.renderLoading();
} else {
return this.renderData(data);
}
}
}
class DataTableStore {
constructor() {
this.cache = {};
this.filter = null;
this.start = 0;
this.end = 10;
}
getDataForPageAndFilter(start, end, filter) {
var url = HttpApiGateway.urlForPageAndFilter(start, end, filter);
// in a better implementation, the HttpApiGateway
// might do the caching automatically, rather than
// making the store keep the cache
if (!this.cache[url]) {
this.cache[url] = DataTableStore.LOADING_TOKEN;
HttpApiGateway.query(url)
.then((response) => {
// success
var payload = {
url: url,
data: response.body
};
dispatch(DATA_FETCH_SUCCESS, payload);
}, (error) => {
// error
dispatch(DATA_FETCH_FAIL, { ... });
});
}
return this.cache[url];
}
handleChangeFilterAction(action) {
this.filter = action.payload.filter;
// the store also decides to reset pagination
this.start = 0;
this.end = 10;
this.emit("change");
}
handleDataFetchSuccessAction(action) {
this.cache[action.payload.url] = data;
this.emit("change");
}
handleDataFetchFailAction(action) {
// ...
}
}
DataTableStore.LOADING_TOKEN = "LOADING"; // some unique value; Symbols work well
You can see that the store is responsible for deciding how to update the pagination and the filter variables, but is not responsible for deciding when HTTP requests should be made. Instead, the view simply requests some data, and if the store doesn't have it in the cache, it will then make the HTTP request.
This also allows the view to pass in any additional local state into the getter (in case the HTTP requests also depends on local state).
I'm not sure to understand all the parts of the question but will try to answer with some useful insights.
Flux is a bit like a not-yet mature version of EventSourcing / CQRS / Domain-Driven-Design for frontend developers
We use something akin to Flux for years on the backend with a different terminology. We can compare Flux ActionCreators to DDD Commands, and Flux Actions to DDD Events.
A command represent the user intent (LOAD_TIMELINE(filters)). It can be accepted or rejected by a command handler that will eventually publish some events. In an UI this does not make much sens to reject commands as you don't want to display buttons that the user should not click...
An event represent something that has happened (always in the past).
The React app state that drives the UI is then somehow a projection of the event log to a json state. Nothing can be displayed on the UI without an event being fired first.
Answering your questions
But what if request's parameters depend on store's state? Nobody seems
to mention it.
In DDD, command handlers can actually be stateful. They can use the app state to know how to handle the command appropriately. Somehow this means that your Flux ActionBuilders can be stateful too (maybe they can use some store data while )
So essentially, based on user interaction with the view, an ACTION is
dispatched. Store owns logic on how to transition from current state0
to the next state1 given ACTION. Data from state1 is needed to
properly form new HTTP request.
In DDD there is a concept called Saga (or Process Manager).
To make it simple, it receives the event stream and can produce new commands.
So basically you can express through a Saga your requirement: when there's an event "FILTERS_UPDATED", fire a command "RELOAD_LIST" with the new filters. I'm pretty sure you can implement something similar with any Flux implementation.
Sagas should rather be disabled when you replay the event log, as replaying the event log should not have side effects like triggering new events.
These kinds of semantics are supported in my Atom-React framework, where stores can act as stateful command handlers or sagas.

Resources