This package has been deprecated

    Author message:

    Favor React native ways (like hooks) over these utils. Submodule routing-history moved to @ackee/redux-utils

    @ackee/chris
    TypeScript icon, indicating that this package has built-in type declarations

    2.0.1 • Public • Published

    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.

    ackee|chris

    GitHub license CI Status PRs Welcome Dependency Status

    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_PAGE is dispatched with all properties (except the type of course) from SET_PAGE_START. The SET_PAGE action 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_USERS is dispatched in application it's caught and handled by getData handler.
    • When getData saga
      • throw an error during its run, action REQUEST_USERS_FAILED with error property is dispatched
      • run without error then action REQUEST_USERS_SUCCEEDED with property result is dispatched, where result is anything that getData saga returns (nothing in our example 😀)
    • Action REQUEST_USERS_COMPLETED is dispatched at the end every time, no matter if running getData failed or succeded

    Little magic explanation:
    Actions dispatched during processing of a saga are automatically created inside the runSagas helper as a composition of the initial action (REQUEST_USERS in our example) and one of the prefixes - _FAILED, _SUCCEEDED or _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

    Alert Important prerequistence for using the HOC is that you must have react-router in your app.

    HOC has same purpose and works almost same as fetchDependencies with few exceptions.

    • It wraps component also with withRouter HOC so all props receive also router's match, location and history objects.
    • 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 shouldReRoute invoke 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>
    );

    Keywords

    none

    Install

    npm i @ackee/chris

    DownloadsWeekly Downloads

    31

    Version

    2.0.1

    License

    MIT

    Unpacked Size

    240 kB

    Total Files

    65

    Last publish

    Collaborators

    • pivl
    • hofp37
    • grissius
    • ackeecz
    • horaklukas
    • borecekbaji
    • davidbilik