@ackee/chris v2.0.1
With React hooks, these utilities are not needed and the package is not maintained anymore.
Only leftover is routing-history which was moved to @ackee/redux-utils.

Chris
Useful additions for your redux - react-router based app.
Name of package refers to Saint Christopher, patron of travellers at their routes. Chris is just its shorter form used for easier remembrance and writability.
Table of contents
Installation
Using npm:
npm i -s @ackee/chris
Using yarn:
yarn add @ackee/chris
API
Selectors
routingSelector
Selector for connected-react-router state, which returns location.
Sagas
runRouteDependencies(handlers: {[ActionType], sagaHandler}, selector: ReduxSelector)
With usage of runRouteDependencies and routing-history, you can exclude business logic from React componentDidMount and componentWillUnmount and download the data you need for the current page outside of React components.
Example - react to new route
import { runRouteDependencies } from '@ackee/chris';
const handlers = {
'/user/:id': function* ({ id }) {
// fetch user data and store it
}
};
export default function* () {
yield all([
takeEvery(LOCATION_CHANGE, runRouteDependencies, handlers),
])
}Example - react to new route and clean data when leaving it
import { runRouteDependencies } from '@ackee/chris';
import routingHistory from '@ackee/chris/routing-history';
const { activeLocationSelectorFactory, previousLocationSelectorFactory } = routingHistory;
// 'history' is the name of routingHistory module state
const activeLocationSelector = activeLocationSelectorFactory('history');
const previousLocationSelector = previousLocationSelectorFactory('history');
const handlers = {
'/user/:id': function* ({ id }) {
// fetch user data and store it
}
};
const postHandlers = {
'/user/:id': function* ({ id }) {
// flush user data from the store
}
};
export default function* () {
yield all([
takeEvery(LOCATION_CHANGE, runRouteDependencies, handlers, activeLocationSelector),
takeEvery(LOCATION_CHANGE, runRouteDependencies, postHandlers, previousLocationSelector),
])
}runRouteActions(handlers: {[ActionType], sagaHandler})
Alias for runRouteDependencies saga.
routeRefresh(initType: ActionType, type: ActionType, handlers: function)
Saga to refresh route dependecies. It run runRouteDependencies(handlers) every time initType action is dispatched.
Also action with type is dispatched before runRouteDependencies is invoked.
Let's say we have an application with more pages that have list of items with paging. Every time page change we want to load new data for particular page.
export default function* () {
yield all([
routeRefresh(
actionTypes.app.SET_PAGE_START,
actionTypes.app.SET_PAGE,
{
'/users': getUsers,
'/invoices': getInvoices,
...
},
),
])
}When user change page of viewed list in application action SET_PAGE_START instead of SET_PAGE should be dispatched.
Then SET_PAGE_START is caught in routeRefresh and following happen
- action
SET_PAGEis dispatched with all properties (except thetypeof course) fromSET_PAGE_START. TheSET_PAGEaction should set the page to the state. - as a next step, route dependencies are procceded with this call
runRouteDependencies({ '/users': getUsers, '/invoices': getInvoices, ... })
runSagas(sagas: {[ActionType]: sagaHandler})
Automatically invokes all given sagas for given event. The invoke of saga is wrapped with try-catch saga that dispatch relevant actions. Look at the example fo better undestanding
function*() getData {
// This is a pseudo code, implementation of getting data from the API
// and setting them back to the state is up to you
const users = yield api.get(config.api.users);
yield put(setUsers(users));
}
export default function*() {
return yield runSagas({
[actionTypes.designs.REQUEST_USERS]: getData,
[actionTypes.designs.REQUEST_OTHER_DATA]: getOtherData,
...
});
}- Once
REQUEST_USERSis dispatched in application it's caught and handled bygetDatahandler. - When
getDatasaga- throw an error during its run, action
REQUEST_USERS_FAILEDwitherrorproperty is dispatched - run without error then action
REQUEST_USERS_SUCCEEDEDwith propertyresultis dispatched, where result is anything thatgetDatasaga returns (nothing in our example 😀)
- throw an error during its run, action
- Action
REQUEST_USERS_COMPLETEDis dispatched at the end every time, no matter if runninggetDatafailed or succeded
Little magic explanation:
Actions dispatched during processing of a saga are automatically created inside therunSagashelper as a composition of the initial action (REQUEST_USERSin our example) and one of the prefixes -_FAILED,_SUCCEEDEDor_COMPLETED.
Modules
Routing history
There is a routing history module for handling history in redux & react-router apps called routingHistory
import routingHistory from '@ackee/chris/routing-history';Utilities
combineDependenciesHandlers(...routeHandlers) => combinedRouteHandlers
Helper to combine dependencies handlers for runRouteDependecies. Accepts infinite number of handlers objects ({'template': sagaHandler}) and returns exactly one for usage in runRouteDependecies. Supports same keys in the handlers objects
Usage
import { routeHandlers as usersHandlers } from 'Modules/users';
import { routeHandlers as reviewsHandlers } from 'Modules/reviews';
export const appHandlers = {
'/': homeSaga,
'/logout': function* () {
// ...
},
};
const combinedRouteHandlers = combineDependenciesHandlers(
appHandlers,
usersHandlers,
reviewsHandlers
);
runRouteDependencies(combinedRouteHandlers);Each module (e.g. Modules/users) may exports its own routeHandlers object and the combineDependenciesHandlers utility handles their merging.
HOC
fetchDependencies(config?: Config) => (Component) => ComponentThatFetchDependencies
High order component used to request data for wrapped component. If you wrap your page components with the HOC it will ensure that data it needs will be requested right after component render.
In default HOC calls fetch function passed through props when component mounts and clear before it unmounts. If it doesn't suit your needs, supply a config that define your own way of handling. All methods in config are optional and if they aren't supplied, default ones are used.
interface Config {
onLoad?: (props) => void;
onUnload?: (props) => void;
shouldReFetch?: (prevProps, nextProps) => boolean;
}Example - Use with default config
const UsersListPageContainer = compose(
connect(
state => ({ users: state.users }),
dispatch => bindActionCreators({
fetch: requestUsers,
clear: deleteUsers
}, dispatch),
),
fetchDependencies(),
)(UsersListPage);
const App = () => (
<div>
<UserListPageContainer />
</div>
);Example - Use with custom config
const UserDetailPageContainer = compose(
connect(
(state: State) => ({
user: state.user,
userId: state.selectedUserId
}),
dispatch => bindActionCreators({
requestUserDetail: requestUser,
clearUserDetail: clearUser,
}, dispatch),
),
fetchDependencies({
onLoad: ({ userId, requestUserDetail }) => {
requestUserDetail(userId);
},
onUnload: ({ clearUserDetail }) => {
clearUserDetail();
},
shouldReFetch: (prevProps, props) => {
return prevProps.userId !=== props.userId;
},
}),
)(UserDetailPage);
const App = () => (
<div>
<UserListPageContainer />
</div>
);routeDependencies(config?: Config) => (Component) => ComponentWithRouteDependencies
Important prerequistence for using the HOC is that you must have
react-routerin your app.
HOC has same purpose and works almost same as fetchDependencies with few exceptions.
- It wraps component also with
withRouterHOC so all props receive also router'smatch,locationandhistoryobjects. - Its config is slightly different especially in method names. Look at the definition:
interface Config { onRouteEnter?: (props) => void; onRouteLeave?: (props) => void; shouldReRoute?: (prevProps, nextProps) => boolean; } - Default implementation of
shouldReRouteinvoke refetch of data every time any part of url change. It's used until you provide your own.
Example - Use with default config
const UsersListPageContainer = compose(
connect(
state => ({ users: state.users }),
dispatch => bindActionCreators({
fetch: requestUsers,
clear: deleteUsers
}, dispatch),
),
routeDependencies(),
)(UsersListPage);
const App = () => (
<Router>
<div>
<Route path="/users" component={UserListPageContainer}/>
</div>
</Router>
);Example - Use with custom config
const UserDetailPageContainer = compose(
connect(
(state: State) => ({ user: state.user }),
dispatch => bindActionCreators({
requestUserDetail: requestUser,
clearUserDetail: clearUser,
}, dispatch),
),
routeDependencies({
onRouteEnter: ({ match, requestUserDetail }) => {
requestUserDetail(match.params.id);
},
onRouteLeave: ({ match, clearUserDetail }) => {
clearUserDetail();
},
shouldReRoute: (prevProps, props) => {
return prevProps.match.params.id !=== props.match.params.id;
},
}),
)(UserDetailPage);
const App = () => (
<Router>
<div>
<Route path="/users/:id" component={UserDetailPageContainer}/>
</div>
</Router>
);4 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago