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.
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.
As a preface, let me mention that I have never used redux-saga or Firebase before today. I'm currently playing around to get a feel for both technologies.
I'm probably just missing a small concept, but I can't seem to get signout to work on my app. I figured that I should probably use call() to manage side effects within a saga, but nothing does the trick.
Here's the saga in question:
export function* deauthenticateUser() {
yield takeLatest(DEAUTHENTICATE_REQUEST, function* () {
try {
yield call(firebase.auth().signOut)
yield put({ type: DEAUTHENTICATE })
}
catch (error) {
yield put({
type: DEAUTHENTICATE_FAILURE,
payload: error,
error: true,
})
}
})
}
I confirmed that calling firebase.auth().signout() directly works, it's only when using call() that I get the error action. Note that there's also no payload when the error gets dispatched.
I checked in Firebase's documentation, and apparently firebase.auth().signout() returns a promise with nothing as it's content. I'm starting to wonder if that wouldn't be the problem, maybe redux-saga does not like having no result in it's promise when using call()?
How would one handle authentication and especially logging out with Firebase and redux-saga?
From a comment from NULL SWEΔT, I had to call yield call([firebase.auth(), firebase.auth().signOut]).
The reason for this is because of JS' context and how this works. More details by reading this and this (read documentation for call([context, fn], ...args)).
I have an epic which dispatches an action that is handled by a redux middleware, which returns a promise once the action is dispatched. The epic looks approximately like this:
const myEpic = action$ =>
action$.pipe(
ofType(SAVE_ACTION),
switchMap(action => [
saveData(action.payload)
])
)
When the SAVE_ACTION is dispatched, it is picked up by the epic, which dispatches an action created by the saveAction action creator.
This resulting action is intercepted by a redux middleware (specifically redux-axios-middleware) which does an HTTP request and converts the result of dispatching such action to a promise, which has the standard behavior of resolving when the HTTP request succeeds and rejecting when the HTTP request fails.
What I would need to do is do additional work once that promise is resolved, within this epic, because I need data contained both in the payload of the initial SAVE_ACTION action and the payload of the HTTP response.
Because the middleware also dispatches actions upon completion of the underlying HTTP request, it would be easy to write an additional epic to do the additional work I need to do in a distinct epic, but I wouldn't have access to the payload of the initial SAVE_ACTION anymore, so I'm trying to figure out if this can be handled all within a single epic, and I haven't found a way so far. I have found online posts like this one, which is very informative, but still doesn't address the issue of awaiting the dispatch of an action, rather than a plain observable.
One approach would be to use merge like this:
import { merge } from 'rxjs/observable/merge';
const myEpic = action$ => {
let initialPayload = null;
return merge(
action$.pipe(
ofType(SAVE_ACTION),
switchMap(action => {
initialPayload = action.payload;
return [saveData(action.payload)]
})
),
action$.pipe(
ofType(SAVE_ACTION_SUCCESS),
mergeMap(action => {
// Use initialPayload here.
})
),
)
}
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.
I would be thankful if someone could point me into a right direction in understanding the Redux architecture.
I should implement "reducer" functions that will handle my actions.
Reducer functions should be combined and create a store.
Lets say I have a LoginForm (React) component, that makes a XHR request to backend API, and receives a JWT token in response.
When I get the response from the API I should dispatch an action like:
store.dispatch({type: "USER_LOGGED_IN",
payload: {username: "john", JWT: "..."});
This updates the state of my application.
What next?
How do I route to to next page? How do I rerender my components (like navbar, etc.) with the logged in username?
Do I use listeners for that?
Let's say you've a method to authorize user:
import { browserHistory } from 'react-router';
// ...
function promisedApiCall(inputData) {
// ...
// api request to backend with input data
// return a promise
}
/*
* on form submit we call this with input data
*/
function authorizeUser(inputData) {
return promisedApiCall(inputData)
.then((response) => store.dispatch({
type: "USER_LOGGED_IN",
payload: {
username: response.userName,
JWT: response.JWT
}
}))
.then(() => browserHistory.push('/success/path/url'))
.catch(() => browserHistory.push('/failure/path/url'));
}
Assuming you have the following prerequisites:
Created redux store and store object is available in the scope where authorizeUser() is executed.
The method promisedApiCall is the function which makes the request to backend with input data from LoginForm.
promisedApiCall should return a promise. [this is really important]
Configured react-router with redux
Once this is complete, app state is updated with user info and also user will be redirected to a new page. This post explains more about programmatically redirecting using react-router.
Access you app state in you component using Redux connect.
Now you have the user info in your component as props.
react-router has a component browserHistory.You can import that like this,
import {browserHistory} from 'react-router';
And to change your route,
browserHistory.push(<route_where_you want_to_go>);
This will let you change the route.