Why Not "pre-connect" Redux Action Creators? - redux

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.

Related

Watching for a redux-saga action in redux

All of my API calls are handled by redux-sagas. I'm creating a heartbeat modal in my app to detect inactivity. Each time a saga goes off I want to clear my setTimeout so I know that the user is active.
My middleware is a basic one at the moment:
const heartbeatMonitor => store => next => action {
if (action['##redux-saga/SAGA_ACTION']) {
clearTimeout(window.myTimeout);
}
window.myTimeout = window.setTimeout(function() {
// send off an action to tell user they are inactive
}, 100000);
}
It seems like looking for this symbol, ##redux-saga/SAGA_ACTION, is the only way to tell if the action is a saga. I see that redux-sagas has a createSagaMiddleware(options) and I tried using effectMiddlewares but it doesn't seem like you have access to the dispatch method in there so I can't send off a new actions.
but it doesn't seem like you have access to the dispatch method in there so I can't send off a new actions.
Not sure whether this is the kind of solution you wanted, but you do have access to the dispatch method where your comment // send off an action to tell user they are inactive is located in your code snippet, as it is exposed by the store object. (this is documented in the Store Methods Section of the store in the redux docs)
Therefore something like the following should satisfy your case:
const heartbeatMonitor => store => next => action {
if (action['##redux-saga/SAGA_ACTION']) {
clearTimeout(window.myTimeout);
}
const { dispatch } = store;
window.myTimeout = window.setTimeout(() => {
dispatch({ type: "USER_INACTIVE" });
}, 100000);
}
Note: I would probably implement this differently (using redux-sagas effects) Maybe this is an option for you too:
Example Saga
import { put, delay } from "redux-saga/effects";
function* inactiveSaga() {
yield delay(100000);
yield put({ type: "USER_INACTIVE" })
}
Example Integration of saga above:
(add the following in your root saga)
//import { takeLatest } from "redux-saga/effects";
takeLatest(() => true, inactiveSaga)
Explanation: Every action will trigger the inactiveSaga (cause () => true). The inactiveSaga will wait 100000ms before dispatching the "inactive action". If there is a new action within this waiting time the previous execution of the inactiveSaga will be canceled (cause takeLatest, see redux-saga effect docs for takeLatest) and started from the beginning again. (Therefore no "inactive action" will be sent and the inactiveSaga will start to wait for these 100000ms again, before being cancelled or completing the delay and dispatching the "inactive action")

Redux - Providing reducer function for create store error

In this document, it wants 3 arguments, the 1st being the reducer.
https://redux.js.org/api/createstore
I understand that there needs to be a function defined, but where would this be coming from? Am I defining it or is this something that is imported?
Right now, I'm getting an error that rootReducer is not defined.
I guess I'm not sure what to do as for this particular assignment, all I needed to do was return JSON data from a provided endpoint.
I've done without the use of Redux/Thunk, but the requirements ask for it, along with Jest.
What would I add to the first argument?
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(...middleware),
window._REDUX_DEVTOOLS_EXTENSION_ ?
window.__REDUX_DEVTOOLS_EXTENSION__() : f => f
));
Please take the time to read through the Redux documentation. Redux expects you to write "reducer functions" that define how the state is initialized and updated.
Specific to the question of store setup, see the docs page on "Configuring Your Store".

What is PERFORM_ACTION action type inside redux?

Inside my Redux store I recently started getting PERFORM_ACTION actions, which wrap my actual actions as follows:
{
type: PERFORM_ACTION,
action: {
type: REAL_ACTION,
payload: 123
}
}
I was not able to find any answer to this pattern neither in the documentations, nor in Google. The only suggestions that Google included were just references to this type of action without any explanation of what is it and why does it appear in applications.
So, what is this action?
redux dev tool extension wrap your actions with it's own action, what you can do is change the order of the middlewares load,
const store = createStore(
rootReducer,
compose(
applyMiddleware(
/* ---- middlewares ---- */
),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
)
);
if you place the the redux devtools middleware before your middlewares, you will get the warped action.

ngrx effect not being called when action is dispatched from component

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.

Angular2 http.get() ,map(), subscribe() and observable pattern - basic understanding

Now, I have an initial page where I have three links. Once you click on the last 'friends' link, appropriate friends Component gets initiated. In there,
I want to fetch/get list of my friends strored into friends.json file.
Till now everything works fine. But I'm still a newbie for angular2's HTTP service using RxJs's observables, map, subscribe concept. I've tried to understand it and read few articles but until I get into practical work, I'm not gonna understand those concepts properly.
Here I have already made plnkr which is working except HTTP related work.
Plnkr
myfriends.ts
import {Component,View,CORE_DIRECTIVES} from 'angular2/core';
import {Http, Response,HTTP_PROVIDERS} from 'angular2/http';
import 'rxjs/Rx';
#Component({
template: `
<h1>My Friends</h1>
<ul>
<li *ngFor="#frnd of result">
{{frnd.name}} is {{frnd.age}} years old.
</li>
</ul>
`,
directive:[CORE_DIRECTIVES]
})
export class FriendsList{
result:Array<Object>;
constructor(http: Http) {
console.log("Friends are being called");
// below code is new for me. So please show me correct way how to do it and please explain about .map and .subscribe functions and observable pattern.
this.result = http.get('friends.json')
.map(response => response.json())
.subscribe(result => this.result =result.json());
//Note : I want to fetch data into result object and display it through ngFor.
}
}
Please guide and explain properly. I know it will be so beneficial to lots of new developers.
Here is where you went wrong:
this.result = http.get('friends.json')
.map(response => response.json())
.subscribe(result => this.result =result.json());
it should be:
http.get('friends.json')
.map(response => response.json())
.subscribe(result => this.result =result);
or
http.get('friends.json')
.subscribe(result => this.result =result.json());
You have made two mistakes:
1- You assigned the observable itself to this.result. When you actually wanted to assign the list of friends to this.result. The correct way to do it is:
you subscribe to the observable. .subscribe is the function that actually executes the observable. It takes three callback parameters as follow:
.subscribe(success, failure, complete);
for example:
.subscribe(
function(response) { console.log("Success Response" + response)},
function(error) { console.log("Error happened" + error)},
function() { console.log("the subscription is completed")}
);
Usually, you take the results from the success callback and assign it to your variable.
the error callback is self explanatory.
the complete callback is used to determine that you have received the last results without any errors.
On your plunker, the complete callback will always be called after either the success or the error callback.
2- The second mistake, you called .json() on .map(res => res.json()), then you called it again on the success callback of the observable.
.map() is a transformer that will transform the result to whatever you return (in your case .json()) before it's passed to the success callback
you should called it once on either one of them.
Concepts
Observables in short tackles asynchronous processing and events. Comparing to promises this could be described as observables = promises + events.
What is great with observables is that they are lazy, they can be canceled and you can apply some operators in them (like map, ...). This allows to handle asynchronous things in a very flexible way.
A great sample describing the best the power of observables is the way to connect a filter input to a corresponding filtered list. When the user enters characters, the list is refreshed. Observables handle corresponding AJAX requests and cancel previous in-progress requests if another one is triggered by new value in the input. Here is the corresponding code:
this.textValue.valueChanges
.debounceTime(500)
.switchMap(data => this.httpService.getListValues(data))
.subscribe(data => console.log('new list values', data));
(textValue is the control associated with the filter input).
Here is a wider description of such use case: How to watch for form changes in Angular.
There are two great presentations at AngularConnect 2015 and EggHead:
Observables vs promises - https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises
Creating-an-observable - https://egghead.io/lessons/rxjs-creating-an-observable
RxJS In-Depth https://www.youtube.com/watch?v=KOOT7BArVHQ
Angular 2 Data Flow - https://www.youtube.com/watch?v=bVI5gGTEQ_U
Christoph Burgdorf also wrote some great blog posts on the subject:
http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html
http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html
In action
In fact regarding your code, you mixed two approaches ;-) Here are they:
Manage the observable by your own. In this case, you're responsible to call the subscribe method on the observable and assign the result into an attribute of the component. You can then use this attribute in the view for iterate over the collection:
#Component({
template: `
<h1>My Friends</h1>
<ul>
<li *ngFor="#frnd of result">
{{frnd.name}} is {{frnd.age}} years old.
</li>
</ul>
`,
directive:[CORE_DIRECTIVES]
})
export class FriendsList implement OnInit, OnDestroy {
result:Array<Object>;
constructor(http: Http) {
}
ngOnInit() {
this.friendsObservable = http.get('friends.json')
.map(response => response.json())
.subscribe(result => this.result = result);
}
ngOnDestroy() {
this.friendsObservable.dispose();
}
}
Returns from both get and map methods are the observable not the result (in the same way than with promises).
Let manage the observable by the Angular template. You can also leverage the async pipe to implicitly manage the observable. In this case, there is no need to explicitly call the subscribe method.
#Component({
template: `
<h1>My Friends</h1>
<ul>
<li *ngFor="#frnd of (result | async)">
{{frnd.name}} is {{frnd.age}} years old.
</li>
</ul>
`,
directive:[CORE_DIRECTIVES]
})
export class FriendsList implement OnInit {
result:Array<Object>;
constructor(http: Http) {
}
ngOnInit() {
this.result = http.get('friends.json')
.map(response => response.json());
}
}
You can notice that observables are lazy. So the corresponding HTTP request will be only called once a listener with attached on it using the subscribe method.
You can also notice that the map method is used to extract the JSON content from the response and use it then in the observable processing.
import { HttpClientModule } from '#angular/common/http';
The HttpClient API was introduced in the version 4.3.0. It is an evolution of the existing HTTP API and has it's own package #angular/common/http.
One of the most notable changes is that now the response object is a JSON by default, so there's no need to parse it with map method anymore .Straight away we can use like below
http.get('friends.json').subscribe(result => this.result =result);

Resources