tl;dr: Within a Redux middleware function, is it okay to dispatch a new action after calling next to finish updating the store?
I'm building a HackerNews reader using Flutter and built-flutter-redux, based off of Brian Egan's TodoMVC example. It uses HN's Firebase-backed API to pull data:
https://github.com/HackerNews/API
My actions look like this right now:
ActionDispatcher<Null> fetchHackerNewsTopStories;
ActionDispatcher<List<int>> fetchHackerNewsTopStoriesSuccess;
ActionDispatcher<Null> fetchHackerNewsTopStoriesFailure;
ActionDispatcher<Null> fetchNextHackerNewsItem;
ActionDispatcher<HackerNewsItem> fetchHackerNewsItemSuccess;
ActionDispatcher<Null> fetchHackerNewsItemFailure;
There's a piece of middleware that listens for the fetchHackerNewsTopStories action and kicks off a call to the API:
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null>
createFetchHackerNewsTopStories(HackerNewsRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
service.fetchHackerNewsTopStories().then((ids) {
return api.actions.fetchHackerNewsTopStoriesSuccess(ids);
}).catchError(api.actions.fetchHackerNewsTopStoriesFailure);
next(action);
};
}
When it returns, I update my app's state with the list of IDs.
At some point I need to dispatch another action, fetchNextHackerNewsItem. There's another middleware function that will listen for that action and request the details for the the first story. When those details arrive, it'll request the next story, and so on until everything's updated.
What I'd like to know is whether I can do this:
// Invoked when REST call for the list of top story IDs completes.
MiddlewareHandler<AppState, AppStateBuilder, AppActions, List<int>>
createFetchHackerNewsTopStoriesSuccess() {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<List<int>> action) {
next(action);
api.actions.fetchNextHackerNewsItem(); // Is this cool?
};
}
// Initiates a request for a single story's details.
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null>
createFetchNextHackerNewsItem(HackerNewsRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
int nextId = api.state.topStoryIds[api.state.loadedUpToIndex];
service.fetchHackerNewsItem(nextId).then((item) {
return api.actions.fetchHackerNewsItemSuccess(item);
}).catchError(api.actions.fetchHackerNewsTopStoriesFailure);
next(action);
};
}
Because createFetchNextHackerNewsItem relies on the app's state (api.state.topStoryIds[api.state.loadedUpToIndex]), I'd like for it to run after the store is updated by the next(action) call.
Is it cool to dispatch new actions in Redux middleware after calling next, or is that some kind of anti-pattern? If it is an anti-pattern, what's the best way to implement this flow?
Yes, it's fine - a middleware can do literally anything it wants when an action is dispatched. That includes modifying / logging / delaying/ swapping / ignoring the original action, as well as dispatching additional actions.
Related
I have GET requests and normally when those succeeded I save data in store, but for POST requests I need to know if it succeeded or not, in order to execute some code (show a message and redirect), the docu says you can use an isLoading variable, but it just says if the service is working but not if it succeeded, if I try to create a new success variable in the store, it will be turned on forever after the request and I don't need that either. I tried returning a promise from the action creator and handle response directly inside the component but it looks like the same to call axios there instead of using redux.
My action creator looks like this:
export function createProject(userId, projectName) {
return function (dispatch) {
dispatch({ type: projectsActions.START_CREATE_PROJECT });
return ProjectsService.createProject(userId, projectName).then(() => {
dispatch({ type: projectsActions.SUCCESS_CREATE_PROJECT });
}).catch((error) => {
dispatch({ type: projectsActions.ERROR_CREATE_PROJECT });
throw error;
});
}
}
I understand where your doubts are coming from, it doesn't seem appropriate to have a field on your Redux store only to know the success of a one-time request.
If you only need to make a post request and only care about it's result once, the simplest way to do it is to use state in the component making the request. Component-level state is easily manageable and gets removed from memory when the component is unmounted, but on the other hand you may want to have a single source of truth for your app. You have to make a choice, but your Redux implementation is correct.
Hey guys I have a function as so:
function dispatchSignup(username, password) {
return function(dispatch) {
const newUser = {username: username, password: password}
axios.post('/signup', newUser).then(() => {
return dispatch(signupAction)
}).then(() => {
return dispatch(push('/'))
}).catch((error) => {console.log(error)})
}
}
This function is first sending a request to my server to signup. If successful, '.then' runs and dispatches a signupAction. I then call another '.then' after this, which should only run after this signupAction has been dispatched, which will redirect the user to '/' aka. my home page. The problem I'm having, is that yes they signup, and the url pushed works, however it's not actually rendering the component at '/'. What is happening here? It's as if they're blocking one another, although I'm not really sure. Redux-thunk is async I thought, so the second action I call won't be dispatched until the first has successfully dispatched.
Any help would be greatly appreciated, thanks.
I then call another '.then' after this, which should only run after this signupAction has been dispatched
This assumption is incorrect. The dispatch function returns the action being dispatched, not a Promise. Assuming signupAction is also using redux-thunk (returning an action), then that would explain why your call to push('/') is happening immediately and not waiting for your signup process to be complete.
As I understand when a request to an event emitter on the server arrives, that request is never closed and you only need to res.write() every time you would like to send a message. However is there a way to be notified when the client that performed this request has left? Is there a property on the request object?
suppose I have the following route
app.get('/event',function(req,res){
//set response headers
//how do I check if req object is still active to send a message and perform other actions?
})
The basic sequence of events should be similar in other frameworks, but this example is Grails 3.3.
First set up endpoints to subscribe, and to close the connection.
def index() {
// handler for GET /api/subscribe
rx.stream { Observer observer ->
// This is the Grails event bus. background tasks,
// services and other controllers can post these
// events, CLIENT_HANGUP, SEND_MSG, which are
// just string constants.
eventBus.subscribe(CLIENT_HANGUP) {String msg ->
// Code to handle when the grails event bus
// posts CLIENT_HANGUP
// Do any side effects here, like update your counter
// Close the SSE connection
observer.onCompleted()
return
}
eventBus.subscribe(SEND_MSG) {String msg ->
// Send a Server Sent Event
observer.onNext(rx.respond(msg))
}
}
}
def disconnecting()
{
// handler for GET /api/disconnect
// Post the CLIENT_HANGUP event to the Grails event bus
notify(CLIENT_HANGUP, 'disconnect')
}
Now in the client, you need to arrange to GET /api/disconnect whenever your use-case requires it. Assuming you want to notice when someone navigates away from your page, you could register a function on window.onbeforeunload. This example is using Vue.js and Axios.
window.onbeforeunload = function (e) {
e.preventDefault()
Vue.$http({
method: 'get',
url: 'http://localhost:8080/api/disconnect'
})
.then((response) => { console.log(response) })
.catch(({error}) => { console.log(error) })
}
In the case of Servlet stacks like Grails, I found that I needed to do this even if I had no housekeeping of my own to do when the browser went away. Without it, page reloads were causing IOExceptions on the back end.
Many redux examples show making HTTP requests directly in an async action; however this will result in an exterme amount of duplicate code common to all requests, for example:
Retrying failed requests based on status code
Appending common HTTP Headers to each request.
Invoking additional http requests based on response (ie: oauth token refresh)
Aborting in-flight requests on route transitions.
My gut feeling is that a middleware could be used to make http requests (in the same vein as redux-api-middleware) which can keep the in-flight requests in the store - however I am also wondering if I'm leaning on bad habbits - is a little duplication a small price to pay for immutability?
Your action creators are just JavaScript functions.
If you have duplication between several functions, you extract the common code into another function. This is no different. Instead of duplicating the code, extract the common code into a function and call it from your action creator.
Finally, this pattern can be abstracted away with a custom middleware. Check out the “real world” example in Redux repo to see how it can be done.
All but the aborting could be accomplished while staying immutable by using a request factory which attaches .then and .catch (or equivalent, depending on promise flavor) to the request before returning it.
you can have a action which executes its operation in addition it calls another action, to achieve this you need to have a redux-async-transitions, the example code is given below
function action1() {
return {
type: types.SOMEFUNCTION,
payload: {
data: somedata
},
meta: {
transition: () => ({
func: () => {
return action2;
},
path : '/somePath',
query: {
someKey: 'someQuery'
},
state: {
stateObject: stateData
}
})
}
}
}
and here is for asynchronous call
function asynccall() {
return {
types: [types.PENDINGFUNCTION, types.SUCCESSFUNCTION, types.FAILUREFUNCTION],
payload: {
response: someapi.asyncall() // only return promise
}
meta: {
transition: (state, action) => ({
onPending: () => {
},
onSuccess: (successdata) => {
//gets response data can trigger result based on data
},
onFail: (promiseError) => {
//gets error information used to display messages
}
})
}
}
}
Calling http request in middleware is the same bad ideas as calling it in action creators. You should focus on making our reducers powerful enough to handle asynchronous effects as well as synchronous state transitions. The best way i found while working with redux is describe effects in the reducer(http request is the effect) and then handle it with library like redux-loop or redux-saga
For avoiding code duplication you can extract common code to request function and use it for handling http effects
Using Signalr (1.0.0-alpha2), I want to know if it is possible to add client functions after a connection has been started.
Say I create my connection and grab the proxy. Then I add some Server Fired client functions to the hub to do a few things. Then I start my connection. I then want to add some more Server Fired functions to my hub object. Is this possible?
var myHub= $.connection.myHub;
myHub.SomeClientFunction = function() {
alert("serverside called 'Clients.SomeClientFunction()'");
};
$.connection.hub.start()
.done(function() {
myHub.SomeNewClientFunction = function() {
alert("serverside called 'Clients.SomeNewClientFunction()'");
}
})
This example is not realistic, but I basically want to send my 'myHub' variable to a different object after the hub is started to subscribe to new events that the original code did not care for.
Real Life Example: A dashboard with a number of different hub events (new site visits, chat message, site error). I 'subscribe' after the connection has started and then pass my hub proxy to all of my different UI components to handle their specific 'message types'. Should I create separate Hubs for these or should I be able to add more Server Fired client functions on the fly?
Yes you can. Use the .on method.
Example:
myHub.on('somethingNew', function() {
alert("This was called after the connection started!");
});
If you want to remove it later on use the .off method.
I have the exact same situation. You might want to consider adding another layout of abstraction if you're trying to call it from multiple places.
Here's a preliminary version of what I've come up with (typescript).
I'll start with the usage. SignalRManager is my 'manager' class that abstracts my debuggingHub hub. I have a client method fooChanged that is triggered on the server.
Somewhere in the module that is using SignalR I just call the start method, which is not re-started if already started.
// ensure signalR is started
SignalRManager.start().done(() =>
{
$.connection.debuggingHub.server.init();
});
Your 'module' simply registers its callback through the manager class and whenever the SignalR client method is triggered your handler is called.
// handler for foo changed
SignalRManager.onFooChanged((guid: string) =>
{
if (this.currentSession().guid == guid)
{
alert('changed');
}
});
This is a simple version of SignalRManager that uses jQuery $.Callbacks to pass on the request to as many modules as you have. Of course you could use any mechanism you wanted, but this seems to be the simplest.
module RR
{
export class SignalRManager
{
// the original promise returned when calling hub.Start
static _start: JQueryPromise<any>;
private static _fooChangedCallback = $.Callbacks();
// add callback for 'fooChanged' callback
static onfooChanged(callback: (guid: string) => any)
{
SignalRManager._fooChangedCallback.add(callback);
}
static start(): JQueryPromise<any>
{
if (!SignalRManager._start)
{
// callback for fooChanged
$.connection.debuggingHub.client.fooChanged = (guid: string) =>
{
console.log('foo Changed ' + guid);
SignalRManager._fooChangedCallback.fire.apply(arguments);
};
// start hub and save the promise returned
SignalRManager._start = $.connection.hub.start().done(() =>
{
console.log('Signal R initialized');
});
}
return SignalRManager._start;
}
}
}
Note: there may be extra work involved to handle disconnections or connections lost.