ngrx/angular 6 - Trying to set params for action and another action through effect - ngrx

I have a component that dispatches some UI actions, and for each of these UI actions, an ngrx store will be updated, and then the LoadData action called. Currently, each action looks something like this:
export class AddCityFilter implements Action {
readonly type = ADD_CITY_FILTER;
constructor(
public payload: string,
public loadParams: LoadParams
) {}
}
Here's an example of what one of the UI actions does in the store:
switch (action.type) {
case fromActions.ADD_CITY_FILTER: {
const cityFilters = [...state.cityFilters, action.payload];
return {
...state,
cityFilters
};
}
and the effect that triggers the load action after any of these UI actions looks like this:
#Effect()
uiUpdate$ = this.actions$.pipe(
ofType(
uiActions.ADD_CITY_FILTER,
uiActions.REMOVE_CITY_FILTER,
uiActions.ADD_TYPE_FILTER,
uiActions.REMOVE_TYPE_FILTER,
uiActions.UPDATE_SORT_COLUMN
),
map((action: uiActions.uiActions) => {
return new opportunitiesActions.LoadOpportunities(action.loadParams);
})
This is working, however, it bothers me that I have to pass the params for the loading action to the to initial action that updates the UI store. It feels wrong because those actions are intended for the UI store and shouldn't be aware of that next loading action ideally, and also because it forces me to add the loadParams property to an action that doesn't even need to pass anything (if I want to include it in the above effect). Is there a way to pass through the loadParams to the second load action without including them in the first UI action?

Related

React Redux Search Reducer

Currently I have the below reducer switch statement. All it does is toggles the state of Sidebar, so first it shows then hides then shows. It's easy.
switch(action.type) {
case 'SIDEBAR_DISPLAY_TOGGLE':
return {
...state,
Sidebar : {
...state.Sidebar,
Display : !state.Sidebar.Display
}
}
default:
return state;
}
Now I have a input field like here
that people can type to search account. I am trying to set up Redux so when user types, it gets saved to the Redux global state and I can pull it from another component. I have this reducer code set up for it but I don't know how can I pull what user types into this reducer from that component?
function reducer(state = initialState, action) {
switch(action.type) {
case 'ACCOUNT_SEARCH':
return {
...state,
AccountNumberSearch : {
...state.AccountNumberSearch,
AccountNumber : ''
}
}
default:
return state;
}
}
}
An action is just an object with a string value named type. Any other properties on this object will also be passed, so you use this to pass the typed text.
If you're using a function to create your actions, something along the lines of:
export function accountNumberSearch(accountNumber) {
return { type: 'ACCOUNT_SEARCH', accountNumber };
}
Then in your reducer, you'll be able to assign the value in the state to action.accountNumber.
AccountNumberSearch : {
...state.AccountNumberSearch,
AccountNumber : action.accountNumber,
}
Then you can map your state to props as you normally would (as you did for the sidebar toggle).
Also, as an aside, you should look into modularising your reducers with combineReducers - Docs
This would be much easier than the way you're doing it.
EDIT: Handling the changes
First of all, you'd want to wire up your input field for the search box to an onChange listener. If you do this like onChange={this.onSearchChange} you can get the value from event in the function:
onSearchChange = event => {
this.props.AccountNumberSearch(event.target.value);
}
Then in mapDispatchToProps you'd send your action + the passed value to dispatch:
const mapDispatchToProps = dispatch => {
return {
AccountNumberSearch: AccountNumber => dispatch(importedActions.AccountNumberSearch(AccountNumber)),
}
}
Then, in the component you want to RECEIVE this value, you'd map the redux state to props, like:
const mapStateToProps = state => {
return {
AccountNumber: state.AccountNumberSearch.AccountNumber,
}
}
Then you can access that value in your render function by calling this.props.AccountNumber.
If you need to do something when this value changes, you can always listen on componentDidUpdate, and compare the value with the old one - if it changed, call whatever function that you need to do.

Should I dispatch multiple actions in a single action creator, or change multiple properties in a single action type?

Say you are fetching data from a database - is it better to use an action creator like:
dispatch(fetchDataStart());
//then on success
dispatch(fetchDataSuccess(data));
And then have the reducer part look like:
case FETCH_DATA_START:
return { ...state, isFetching:true };
case FETCH_DATA_SUCCESS:
return { ...state, isFetching:false, data:action.data };
Or is it better to separate the fetching-logic from the data-logic and do something like this:
dispatch(fetchDataStart());
//then on success
dispatch(fetchDataFinish());
dispatch(updateData(data));
reducer:
case FETCH_DATA_START:
return { ...state, isFetching:true };
case FETCH_DATA_FINISH:
return { ...state, isFetching:false };
case UPDATE_DATA:
return { ...state, data:action.data };
The initial. The latter doesn't make much sense unless it's executed async.
Normally you would use an action to describe your intent fetchData is a reasonable name. A reducer will set the state to loading. Than the middleware will run with the the action and afterwards the middleware will trigger an action to inform the fetch is done, something like fetchSuccess and fetchError. You're allowed to do whatever is in the middle. If you want to trigger a FETCH_DATA_LOADING action after your fetchData action, because you have need for a very cool reducer to listen for it, please do so.The same argument for when the middleware returns. Please keep your actions as generic as possible so the middleware logic can be reused.

How to get previous state from Router_Cancel in ngrx?

I was trying to restrict the user from navigating away from the current page by using CanDeactivate (if form is dirty and not saved). By the time we click on any link, Router_Navigation event is getting called and it is updating the router state in store and if I cancel the page navigation on modal pop up (from can deactivate), Router_Cancel event is being called, but the current router state is not getting updated (it’s still pointing to other page).
I saw this in ngrx documentation:
ROUTER_CANCEL and ROUTER_ERROR contain the store state before the
navigation. Use the previous state to restore the consistency of the
store.
Can someone please help me on how to get previous state from Router_cancel Action.
Thanks
I solved this by creating an applicationRouter state to maintain current and previous routes, whenever ngrx router dispatches a ROUTER_NAVIGATION Event i am listening to it and updating my applicationRouterState.
At each point applicationRouter will have only two router events (current and previous state).
and whenever Router_Cancel is triggered i am toggling the previous router and current router state.
PFB, the soln:
#Effect()
navigationListener$ = this.actions$.ofType('ROUTER_NAVIGATION')
.pipe(
switchMap((routerNavigationAction: RouterNavigationAction<RouterDefinition>) => {
return of(routerNavigationAction).pipe(
withLatestFrom(this._routerNavigationData$),
map(([action, routerNavData]: [RouterNavigationAction<RouterDefinition>, RouterState]) => {
// TODO: Move this logic to Reducer
if (!(routerNavData.currentRouter && routerNavData.currentRouter.url
&& routerNavData.previousRouter && routerNavData.previousRouter.url)) {
routerNavData.previousRouter = routerNavData.currentRouter = action.payload.routerState;
} else {
routerNavData.previousRouter = routerNavData.currentRouter;
routerNavData.currentRouter = action.payload.routerState;
}
return new fromActions.MaintainPrevCurrRouterStateAction(routerNavData);
})
);
})
);
And this is my state object:
export interface RouterDefinition {
url: string;
queryParams: Params;
params: Params;
segments: string[];
}
export interface RouterState {
currentRouter: RouterDefinition;
previousRouter: RouterDefinition;
}
I use a ngrx/effect to store the latest two ROUTER_NAVIGATION actions, and re-dispatch the previous one when I get a ROUTER_CANCEL or ROUTER_ERROR so that the router state is completely restored.
#Injectable()
export class RouterEffects {
private previousRouterNavigationAction: RouterNavigationAction<
RouterStateUrl
>;
private currentRouterNavigationAction: RouterNavigationAction<
RouterStateUrl
>;
#Effect({ dispatch: false })
save$: Observable<Action> = this.actions$.pipe(
ofType(ROUTER_NAVIGATION),
switchMap((action: RouterNavigationAction<RouterStateUrl>) => {
this.previousRouterNavigationAction = this.currentRouterNavigationAction;
this.currentRouterNavigationAction = { ...action };
return Observable.empty();
})
);
#Effect()
load$: Observable<Action> = this.actions$.pipe(
ofType(ROUTER_CANCEL, ROUTER_ERROR),
switchMap(action => Observable.of(this.previousRouterNavigationAction))
);
constructor(private actions$: Actions) {}
}

Where does that "selectedSubreddit" come from in Redux Reddit API example

All:
I am pretty new to Redux, when I follow its Reddit API example, there is one code snippet confuse me so much:
In AsyncApp.js, there is:
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
I wonder where the dispatch and selectedSubreddit get bind to this.props?
Thanks
That example is using the connect() function from react-redux to inject certain parts of the Redux state and the store's dispatch() function as props in that component. See the 'Usage With React' part of the Redux docs for more information.
For example:
App.js:
export class App extends Component {
//...
}
function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(App)
The connect() function here is taking the mapStateToProps() function above to inject the appropriate parts of the Redux state as props in the <App /> component. The keys of the object returned by mapStateToProps() correspond to the names of the props injected, and the corresponding values are the values of those injected props.
connect() can also take a second argument, matchDispatchToProps(), which can be used to inject specific action dispatch functions as props in your component. Whether or not you supply any arguments to connect(), it will inject your Redux store's dispatch() function as a prop called dispatch.
These connected container components receive state updates from the store, so when your Redux state changes, the connected container components will receive new props accordingly.

Am I using Redux correctly?

this is just a question,
I'd love to double check if I'm doing things right. I'm coming from ages of different frameworks, and I def want to avoid bad practices in the early stage.
I'm using this boilerplate: https://github.com/erikras/react-redux-universal-hot-example
and I'm writing in ES7.
I created:
- 1 reducer: earlyUser.js
- 1 container: landingPage.js
- 1 component: registrationForm.js
In the landingPage, I'm including the methods from reducer in this way:
import { saveEmail, savePreferences, checkEmailExists } from 'redux/modules/earlyUser';
and I declare some handles
handleSubmitEmail(data) {
this.props.saveEmail(data);
}
handleSubmitPreferences(data) {
//some data manipulation
this.props.savePreferences(data);
}
and in the JSX part I just pass to my component the handlers:
<registrationForm submitEmail= {::this.handleSubmit} etc... >
Now inside the component, I linked the form submission to this handler:
submitEmail() {
if (this.validateEmail(this.state.email)) {
this.props.submitEmailHandler(this.state);
}
}
Now my question is, where should I attach the .then and .catch of the promise returned ?
Ideally I'd like to do inside the component.js something like
this.props.submitEmailHandler(this.state).then( function emailRegisterCallback(){
// move to next step
}).catch( function errorHandler(error){
// there was an error
}
Is that correct?
Also, is there the right syntax to handle promises in ES7 ?
You normally handle the async aspects in your action creators:
See this code from the async redux example:
function fetchPosts(reddit) {
return dispatch => {
dispatch(requestPosts(reddit));
return fetch(`http://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(reddit, json)));
};
}
When the promise resolves, you should dispatch another action to update the state with the successful result or the error.

Resources