I am building my own SaaS application using React library for the user interface. What I like about react you don’t have to use it in a SPA (single page application).
In my SaaS application, I created several independent React applications. One for the dashboard, one for product management, one repositories connection, and one for payment. There are two fundamental parts in every react application I build:
For dropdown select on the user interface, I like the React Select. It’s beautiful and has a lot of features.
The “React Select” is:
“A flexible and beautiful Select Input control for ReactJS with multiselect, autocomplete, async and creatable support.”
I load the select box with the list of items I get from the API. The concrete API is GitHub which return a limited number of user repository branches. If the repository has a lot of branches, we have to make several API calls to get the full list. The solution is to make another API call only if the user scrolls at the bottom of the list. If a user doesn’t open the select box or not scroll to the end, we will not make the unnecessary API call.
The React Select doesn’t offer this out of the box, but there are options:
React Select Async Paginate
fork which is a wrapper for react-select
that supports pagination on a menu scroll.The react select async paginate library seems like an actively maintained one, and for that reason, I decided to try it out. Unfortunately, it doesn’t fit well with Redux Observable. In "Async Paginate" library, we have to pass the async method which will return options.
I prefer to dispatch an action and make ajax call with middleware. I like to keep my async calls in one place, in an “epic” folder where epic Redux Observable lives.
The redux observable is a middleware for Redux that allows you to filter and map action using RxJS operators. It can handle cancellation and other side effects. In terms of how code is structured all async calls will be stored into one location what makes the code cleaner and organized.
Concepts related to Redux Observable and RxJs can be a bit harder to grasp, so give yourself time to read, watch some courses, and to practice.
Since the React async paginate library does not fit well with Redux Observable, I decided to write my pagination implementation using Redux, and Redux Observable. I liked the “Redux Observable” approach much more than using React Select Async Paginate fork. The reason is with “Redux Observable” the ajax API calls are separated in one folder. No need to search through the call to find where API calls are.
And finally, let’s see some code.
Actions are simple functions that return action type and related data we need to execute that action. We created an action for fetching repository branches from the GitHub API, and one more action to dispatch when fetching data is done with success.
export const FETCH_MORE_BRANCHES = 'FETCH_MORE_BRANCHES';
export const SUCCESS_FETCH_BRANCHES = 'SUCCESS_FETCH_BRANCHES';
export function fetchMoreBranches(identityId, nextPage) {
return {
type: FETCH_MORE_BRANCHES,
identityId,
nextPage
};
}
export function successFetchBranches(branches, nextPage) {
return {
type: SUCCESS_FETCH_BRANCHES,
branches,
nextPage
};
}
To store the next page API URL we add new attribute into the redux store: the
branchesNextPage
attribute. When it’s value is false app will know that there is no more data to load. const preloadedState = {
repositories: {
identityId,
branches: [],
branchesNextPage: false,
}
};
let store = createStore(reducers, preloadedState, composeEnhancers(applyMiddleware(epicMiddleware)));
The Redux store is then extracted to the component who needs that information with
mapStateToProps
.In Redux, we distinct presentational and container components. Presentational components render HTML. The container component defines the data and behavior for the presentational components. The container map the data and the actions from the redux store to the presentational component.
We use redux
connect()
to connect the React component with the redux store.const mapStateToProps = state => ({ branchesNextPage: state.repositories.branchesNextPage });
const mapDispatchToProps = dispatch => ({
fetchMoreBranches: (id, nextPage) => dispatch(fetchMoreBranches(id, nextPage))
});
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
The React Select component has a callback prop
onMenuScrollToBottom
which is triggered each time the user scroll options to the bottom. This is the event we will use to load more items to the select box. The
fetchMoreBranches
will dispatch the action and the Redux Observable middleware will execute async ajax call if there is more data to load.<Select
options={branches}
onChange={this.onBranchChange}
value={this.state.selectedBranch}
onMenuScrollToBottom={() =>
this.props.fetchMoreBranches(this.props.identityId, this.props.branchesNextPage)
}
/>
Now we came at the most exciting part of this implementation, the redux observable middleware (epic).
When the action
FETCH_MORE_BRANCHES
is dispatched the Redux Observable middleware will be called and async call will be executed. When an async call responds, it will dispatch the new action
successFetchBranches
passing branches to it, and nextPage
if the next page exists. This action SUCCESS_FETCH_BRANCHES
will be caught by reducer to update the redux state. In case we catch the error from the backend the
catchError
will execute Alert box with an error message.import { FETCH_MORE_BRANCHES, successFetchBranches } from '../actions';
import { ofType } from 'redux-observable';
import { EMPTY, of } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { mergeMap, map, catchError } from 'rxjs/operators';
import tokenHeader from '../../common/helpers/token';
import { showAlertBox } from '../../common/layout';
const fetchLoadMoreBranchesEpic = action$ => {
return action$.pipe(
ofType(FETCH_MORE_BRANCHES),
mergeMap(({ identityId, nextPage }) => {
if (nextPage === false) {
return EMPTY;
}
return ajax.post(`/app/auto/repos/branches/${identityId}`, { nextPage }, tokenHeader()).pipe(
map(data => successFetchBranches(data.response.branches, data.response.nextPage)),
catchError(error => of(showAlertBox('danger', error.response.message)))
);
})
);
};
export default fetchLoadMoreBranchesEpic;
The reducer job is to update the redux state and to concatenate loaded branches with an already loaded one.
import React from 'react';
import { SUCCESS_FETCH_BRANCHES } from '../actions';
const repositories = (state = {}, action = []) => {
switch (action.type) {
case SUCCESS_FETCH_BRANCHES: {
return { ...state, branches: state.branches.concat(action.branches), branchesNextPage: action.nextPage };
}
default: {
return state;
}
}
return state;
};
export default repositories;
Now let’s see how loading pagination looks like in action. When scroll reaches the end, the ajax call will load more items into the select box.
The Redux in combination with Redux Observable will make React applications organized and easier to maintain and add more features.
I suggest you go to the Redux Observable documentation and explore this powerful library.