I'm trying to build a React + Redux app, and I'm using Redux Thunk.
One of my action creators looks like this:
import api from '../api';
export const FETCH_BOOKS = 'FETCH_BOOKS';
export default () => dispatch =>
api
.books()
.perPage(-1)
.then(books =>
dispatch(books => ({
type: FETCH_BOOKS,
books,
}))
)
.catch(error =>
dispatch(e => ({
type: 'ERROR',
error: e,
}))
);
But when I run yarn run build:production I get the error(s):
ERROR in ./scripts/ll-app/actions/fetch-books.js
/Users/joneslloyd/resources/assets/scripts/ll-app/actions/fetch-books.js
9:11 warning 'books' is defined but never used no-unused-vars
11:9 error Expected an assignment or function call and instead saw an expression no-unused-expressions
11:9 error 'books' is already declared in the upper scope no-shadow
17:12 warning 'error' is defined but never used no-unused-vars
19:9 error Expected an assignment or function call and instead saw an expression no-unused-expressions
19:9 error 'error' is already declared in the upper scope
However, I want to pass the books array (returned from the async api call) to the dispatch (anonymous function passed to dispatch) to then include said books array in the action, which the reducer will receive.
Am I doing something wrong here?
Even when I rename the inner reference to books it doesn't help.
It's possible I'm overlooking something in ES6 here.. But I basically want to take the books array returned from the api call (as a parameter of the then method), and then pass it into the dispatch function inside of that, as a parameter of the anonymous function I'm passing in.
Any help with this would be excellent. Thanks!
I'm not sure if this is the source of the problem, but why do you need the inner ref to books at all? Your error msg/linter is complaining about that.
...
api
.books()
.perPage(-1)
.then(books =>
dispatch({
type: FETCH_BOOKS,
books,
})
).catch(error => dispatch({type: ERROR, error}))
why won't the above do what you want?
no function in the dispatch necessary here.
dispatch needs a plain action. A function in dispatch is giving you the error.
When you see function in dispatch in the docs, those functions are function calls that are just returning the action.
export someActionCreator = () => ({type: ACTION, payload})
dispatch(someActionCreator());
Your functions are just statements and are not returning the action to the dispatch. which would be more akin to something like
export someActionCreator = () => ({type: ACTION, payload})
dispatch(someActionCreator);
see the difference?
Hope this helps!
Related
We are integrating the NGRX library in a project at the company where we work and we want to perform optimistic updates at the front-end instead waiting for the server response to perform some action. What we have tried is to use the startWith operator, but it throws the Action properly and then, as the releaseService.deleteRelease method does not return an action, it throws the invalid action: null error.
We have tried to add the {dispatch: false} config to the #Effect, but then the first action is not thrown...
We have also though about using a tap oeprator and dispatch some action directly to the store, but we consider it an anti pattern.
So, is it possible to achieve this without creating an splitter intermediate action? Thanks.
#Effect()
deleteRelease$ = this.actions$.pipe(
ofType(ReleaseCardActions.ReleaseCardActionTypes.DeleteRelease),
exhaustMap((action: ReleaseCardActions.DeleteRelease) => {
return this.releaseService.deleteRelease(action.id).pipe(
startWith(new DeleteReleaseSuccess(action.id)),
catchError(() => of(new ReleasesApiActions.DeleteReleaseFailure()))
);
}),
);
Maybe I don't understand the question, but why not perform the optimistic update on the DeleteRelease action directly in the reducer, so your reducer and effect will fire on the same action independently.
Then, you can do the "real" update from the response coming from the effet.
#Effect()
deleteRelease$ = this.actions$.pipe(
ofType(ReleaseCardActions.ReleaseCardActionTypes.DeleteRelease),
exhaustMap((action: ReleaseCardActions.DeleteRelease) => {
return this.releaseService.deleteRelease(action.id).pipe(
map(new DeleteReleaseSuccess(action.id)),
catchError(() => of(new ReleasesApiActions.DeleteReleaseFailure()))
);
}),
);
In a typical React/Redux codebase you have action creator functions like:
Actions.js:
export const addFoo = foo => ({ foo, type: 'ADD_FOO' });
Then you use connect to create a version of that function which dispatches the action, and make it available to a component:
Component.js:
import { addFoo } from 'Actions';
const mapPropsToDispatch = { addFoo };
const SomeComponent = connect(mapStateToProps, mapPropsToDispatch)(
({ addFoo }) =>
<button onClick={() => addFoo(5)}>Add Five</button>;
)
I was thinking, rather than mapping each action creator to its dispatched version inside the connect of every component that uses them, wouldn't it be simpler and cleaner if you could just "pre-connect" all of your action creators ahead of time:
Store.js:
import { createStore } from 'redux'
const store = createStore(reducer, initialState);
export const preConnect = func => (...args) => store.dispatch(func(...args));
Actions.js (2.0):
import { preConnect } from 'Store';
export const addFoo = preConnect(foo => ({ foo, type: 'ADD_FOO' }));
Component.js (2.0):
import { addFoo } from 'Actions';
const SomeComponent = () =>
<button onClick={() => addFoo(5)}>A Button</button>;
Am I missing any obvious reason why doing this would be a bad idea?
You make a reference to the dispatch() function in your code here:
export const preConnect = func => (...args) => store.dispatch(func(...args));
But in the world of React-Redux there is no direct reference to the dispatch() function inside of our components. So what's going on?
When we pass our action creator into the connect() function, the connect() function does a special operation on the functions inside the actions object.
export default connect(mapStateToProps, { selectSong })(SongList);
The connect() function essentially wraps the action into a new JavaScript function. When we call the new JavaScript function, the connect() function is going to automatically call our action creator, take the action that gets returned and automatically call the dispatch() function for us.
So by passing the action creator into the connect() function, whenever we call the action creator that gets added to our props object, the function is going to automatically take the action that gets returned and throw it into dispatch function for us.
All this is happening behind the scenes and you don't really have to think about it when using the connect() function.
So thats how redux works, there is a lot of wiring up and its one of the chief complaints I believe people have around this library, so I do understand your sentiment of wanting to pre-configure some of its setup and in this case, in my opinion, the toughest part of the Redux setup which is wiring up the action creators and reducers.
The problem with pre-configuring I am thinking is that the developer still needs to know how to write these functions and then manually hook them together as opposed to how its done in other state management libraries and if that is taken away by some type of pre-configuration process, Redux becomes more magical and harder to troubleshoot I think. Again the action creators and reducers are the biggest challenge in putting together a Redux architecture and so mastering and knowing how to troubleshoot that area almost requires manual setup to do so.
I am having an issue with the ngrx store not dispatching an action to the effect supposed to deal with it.
Here is the component that tries to dispatch:
signin() {
this.formStatus.submitted = true;
if (this.formStatus.form.valid) {
this.store.dispatch(new StandardSigninAction(this.formStatus.form.value.credentials));
}
}
The actions:
export const ActionTypes = {
STANDARD_SIGNIN: type('[Session] Standard Signin'),
LOAD_PERSONAL_INFO: type('[Session] Load Personal Info'),
LOAD_USER_ACCOUNT: type('[Session] Load User Account'),
RELOAD_PERSONAL_INFO: type('[Session] Reload Personal Info'),
CLEAR_USER_ACCOUNT: type('[Session] Clear User Account')
};
export class StandardSigninAction implements Action {
type = ActionTypes.STANDARD_SIGNIN;
constructor(public payload: Credentials) {
}
}
...
export type Actions
= StandardSigninAction
| LoadPersonalInfoAction
| ClearUserAccountAction
| ReloadPersonalInfoAction
| LoadUserAccountAction;
The effect:
#Effect()
standardSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.STANDARD_SIGNIN)
.map((action: StandardSigninAction) => action.payload)
.switchMap((credentials: Credentials) =>
this.sessionSigninService.signin(credentials)
.map(sessionToken => {
return new LoadPersonalInfoAction(sessionToken);
})
);
I can see in debug that the component does call the dispatch method. I can also confirm that StandardSigninAction is indeed instantiated because the breakpoint in the constructor is hit.
But the standardSignin$ effect is not called...
What can possibly cause an effect not being called?
How can I debug what is going on within the store?
Can someone please help?
P.S. I do run the above effect as follows in my imports:
EffectsModule.run(SessionEffects),
edit: Here is my SessionSigninService.signin method (does return an Observable)
signin(credentials: Credentials) {
const headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'});
const options = new RequestOptions({headers: headers});
const body = 'username=' + credentials.username + '&password=' + credentials.password;
return this.http.post(this.urls.AUTHENTICATION.SIGNIN, body, options).map(res => res.headers.get('x-auth-token'));
}
This is not going to be a definitive answer, but, hopefully, it will be helpful.
Before you start:
Make sure you are using the latest versions of the #ngrx packages (that are appropriate for the version of Angular you are using).
If you've updated any packages, make sure you re-start your development environment (that is, restart the bundler, the server, etc.)
If you've not done so already, you should have a look at the implementation of the Store - so that you make some educated guesses as to what could be going wrong. Note is that the Store is pretty light. It's both an observable (using the state as its source) and an observer (that defers to the dispatcher).
If you look at store.dispatch you'll see that it's an alias for
store.next, which calls next on the Dispatcher.
So calling:
this.store.dispatch(new StandardSigninAction(this.formStatus.form.value.credentials));
should just see an action emitted from the dispatcher.
The Actions observable that's injected into your effects is also pretty light. It's just an observable that uses the Dispatcher as its source.
To look at the actions that are flowing through the effect, you could replace this:
#Effect()
standardSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.STANDARD_SIGNIN)
with this:
#Effect()
standardSignin$: Observable<Action> = this.actions$
.do((action) => console.log(`Received ${action.type}`))
.filter((action) => action.type === session.ActionTypes.STANDARD_SIGNIN)
ofType is not an operator; it's a method, so to add do-based logging, it needs to be replaced with a filter.
With the logging in place, if you are receiving the action, there is something wrong with the effect's implementation (or maybe the action types' strings/constants aren't what you think they are and something is mismatched).
If the effect is not receiving the dispatched action, the most likely explanation would be that the store through which you are dispatching the StandardSigninAction is not that same store that your effect is using - that is, you have a DI problem.
If that is the case, you should look at what differs from the other SessionEffects that you say are working. (At least you have something working, which is a good place to start experimenting.) Are they dispatched from a different module? Is the module that dispatches StandardSigninAction a feature module?
What happens if you hack one of the working SessionEffects to replace its dispatched action with StandardSigninAction? Does the effect then run?
Note that the questions at the end of this answer aren't questions that I want answered; they are questions that you should be asking yourself and investigating.
Your store's stream may be stopping because of either unhandled errors or - perhaps more confusingly - errors that seem 'handled' using .catch that actually kill the stream without re-emitting a new Observable to keep things going.
For example, this will kill the stream:
this.actions$
.ofType('FETCH')
.map(a => a.payload)
.switchMap(query => this.apiService.fetch$(query)
.map(result => ({ type: 'SUCCESS', payload: result }))
.catch(err => console.log(`oops: ${err}`))) // <- breaks stream!
But this will keep things alive:
this.actions$
.ofType('FETCH')
.map(a => a.payload)
.switchMap(query => this.apiService.fetch$(query)
.map(result => ({ type: 'SUCCESS', payload: result }))
.catch(e => Observable.of({ type: 'FAIL', payload: e}))) // re-emit
This is true for any rxjs Observable btw, which is especially important to consider when broadcasting to multiple observers (like ngrx store does internally using an internal Subject).
I am using a later version of ngrx (7.4.0), so cartant's suggestion of:
.do((action) => console.log(`Received ${action.type}`))
should be...
... = this.actions.pipe(
tap((action) => console.log(`Received ${action.type}`)),
...
And in the end I discovered I had missed adding my new effects export to module, like:
EffectsModule.forRoot([AuthEffects, ViewEffects]), // was missing the ', ViewEffects'
If you are using version 8, ensure you wrap each action with createEffect.
Example:
Create$ = createEffect(() => this.actions$.pipe(...))
Another possible reason is that if you used ng generate to create the module where you imported the Effects make sure it is imported in the App Module as the following command 'ng generate module myModule' will not add it to the app module.
I've got a pretty straight forward effect defined using the ngrx/effects library.
#Effect()
public Authorize$ = this._actions$.ofType(IdentityActionsService.AUTHORIZE_IDENTITY)
.switchMap(action => this._svc.Authorize$(action.payload))
.catch(err => Observable.of(null).do(() => console.error(err); }))
.map(identity => this._identity.OnIdentityAuthorized(identity))
The #Effect is triggered, authorize$() runs, and the OnIdentityAuthorized() method, which returns an Action ({type: payload: }) fires...
What I expect to happen is that the action returned by OnIdentityAuthorized() should get fed into the appropriate reducer - that is not happening.
I have a debugger call in OnIdentityAuthorized and in the corresponding reducer. The Action returned by OnIdentityAuthorized is not being dispatched. What might cause this? Am I misunderstanding something?
I feel like what I've got is basically identical to example 1 here: https://github.com/ngrx/effects/blob/master/docs/intro.md
EDIT
Added additional code sections... The effect triggers the OnIdentityAuthorized debugger statement, so the observable is emitting all the way through the async authorization call. The reducer case is not triggered...
Here is the OnIdentityAuthorized() implementation:
public static ON_IDENTITY_AUTHORIZED = '[IDENTITY] Authorized';
public OnIdentityAuthorized(identity: Identity | JWT): Action {
debugger;
return {
type: IdentityActionsService.ON_IDENTITY_AUTHORIZED,
payload: identity
};
}
Here is the reducer section:
switch (action.type) {
case IdentityActionsService.ON_IDENTITY_AUTHORIZED:
debugger;
return merge({}, action.payload);
Turns out to have been an issue with the way I was registering the reducers.
I was trying to do something exotic in order to add additional state endpoints for lazy loaded modules... turns out there is an issue there - but that is a different question.
I am using redux-observable in my redux react app. Given below is the code where I wire everything up. I am using redux-dev tools as well.
const rootEpic = combineEpics(
storeEpic
);
const epicMiddleware = createEpicMiddleware(rootEpic);
//Combine the reducers
const reducer = combineReducers({
syncSpaceReducer,
routing: routerReducer
});
const loggerMiddleware = createLogger();
const enhancer = compose(
applyMiddleware(thunkMiddleware, loggerMiddleware, epicMiddleware),
DevTools.instrument()
);
const store = createStore(reducer, enhancer);
My epic has the following code
export const storeEpic = action$ =>
action$.ofType('FETCH_STORES')
.mapTo({
type: 'FETCHHING_STORES'
});
Now when I run the app I get the following error
Uncaught TypeError: Cannot read property 'apply' of undefined
What am I doing wrong here?
I'm not sure it's possible for us to say. The code you provided all seems fine. The easiest way to know what's wrong is to look at the stack trace for that error. Where does it happen? What led up to it happening? Set a breakpoint there or use "Pause on exceptions" in your debugger to see what exactly is trying to call apply on a variable that is undefined--why is it undefined? That's ultimately the real question.
Edit based on your comments.
The problem is that what you're passing to combineEpics isn't actually your epic, it's undefined. Often that happens when your epic import has a typo or is not exported as you expect. Pause your debugger right before combineEpics and you'll see which one is undefined (or all of them).
Just import storeEpic in the file where combineEpics is called and it will work fine