1.2.0 ā€¢ Published 3 years ago

@calvear/react-redux v1.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

React Redux

React library for eases Redux initialization for React SPA application. Is preconfigured and included helpful libraries as redux-logger and reselect.

Structure šŸ“‹

ā”œā”€ā”€ README.md
ā”œā”€ā”€ LICENCE.md
ā”œā”€ā”€ CHANGELOG.md
ā”œā”€ā”€ .vscode/ # vscode shared development config
ā”œā”€ā”€ src/
ā”‚Ā Ā  ā”œā”€ā”€ effects/ # extra saga effects
ā”‚Ā Ā  ā”œā”€ā”€ hooks/ # extra redux hooks
ā”‚Ā Ā  ā”œā”€ā”€ reselect/ # reselect export bypass
ā”‚Ā Ā  ā”œā”€ā”€ utils/ # action types utils
ā”‚Ā Ā  ā”œā”€ā”€ middleware.js # middleware loader
ā”‚Ā Ā  ā”œā”€ā”€ store.js # createStore
ā”‚Ā Ā  ā””ā”€ā”€ index.js
ā”œā”€ā”€ package.json
ā”œā”€ā”€ jsconfig.js
ā”œā”€ā”€ .babelrc
ā”œā”€ā”€ .eslintrc.json
ā””ā”€ā”€ .prettierrc.json
  • store.js: exports createStore wrapper function and StoreProvider.
  • middleware.js: initializes middleware, redux-logger.

How To Use šŸ’”

Should be initialized with StoreProvider on App.jsx like:

import { StoreProvider } from '@calvear/react-redux';
import store from 'store';

export default function App() {
    return (
        <StoreProvider store={store}>
            <h1>Welcome to My App!</h1>
        </RouterProvider>
    );
}

So, you can create your first reducer, for example in store folder:

ā”œā”€ā”€ ...
ā”œā”€ā”€ store/
ā”‚Ā Ā  ā”œā”€ā”€ sample/
ā”‚Ā Ā  ā”‚Ā Ā  ā”‚   ā”œā”€ā”€ sample.partition.js # contains actions types and partition/store states
ā”‚Ā Ā  ā”‚Ā Ā  ā”‚   ā”œā”€ā”€ sample.reducer.js # reducer
ā”‚Ā Ā  ā”‚Ā Ā  ā”‚   ā””ā”€ā”€ sample.saga.js # saga middleware
ā”‚Ā Ā  ā””ā”€ā”€ index.js # exports default store
ā”œā”€ā”€ App.jsx
ā””ā”€ā”€ index.js

Define your partition definition, containing partition key and actions types in sample.partition.js:

export default {
    // partition key
    Key: 'SAMPLE',

    // action types
    Type: {
        EXEC: 'EXEC',
        COMMIT: 'COMMIT',
        ROLLBACK: 'ROLLBACK',
    },

    // partition states
    State: {
        PREPARING: 'PREPARING',
        EXECUTING: 'EXECUTING',
        READY: 'READY',
        FAILED: 'FAILED',
    },
};

Define your reducer in sample.reducer.js:

import SamplePartition from './sample.partition';

export default function SampleReducer(store = {}, action) {
    const { type, payload } = action;

    switch (type) {
        // executes the action.
        case SamplePartition.Type.EXEC:
            return {
                ...store,
                state: SamplePartition.State.EXECUTING,
                data: payload,
            };

        // action is successful.
        case SamplePartition.Type.COMMIT:
            return {
                ...store,
                state: SamplePartition.State.READY,
            };

        // action was finished with errors.
        case SamplePartition.Type.ROLLBACK:
            return {
                ...store,
                state: SamplePartition.State.FAILED,
                error: payload,
            };

        // default doesn't changes the store,
        // so, components won't re-renders.
        default:
            return store;
    }
}

Finally (optional) your middleware saga in sample.saga.js:

import { all, call, dispatch, takeLatest } from '@calvear/react-redux/effects';
import SamplePartition from './sample.partition';
import Service from 'adapters/service';

function* exec({ payload }) {
    try {
        const data = yield call(Service.GetData);

        // Success action.
        yield dispatch(SamplePartition.Type.COMMIT, data);
    } catch (e) {
        yield dispatch(SamplePartition.Type.ROLLBACK, {
            stacktrace: e,
            message: 'Operation cannot be completed',
        });
    }
}

export default function* run() {
    yield all([
        // use all only if exists two or more listeners.
        takeLatest(SamplePartition.Type.EXEC, exec),
    ]);
}

Finally, your store/index.js file should looks like:

import { createStore } from '@calvear/react-redux';
import { SamplePartition, SampleReducer, SampleSaga } from './sample';

const reducers = {
    [SamplePartition.Key]: SampleReducer,
};

const sagas = [SampleSaga()];

export default createStore({ reducers, sagas, true });

Hooks

Library has custom hooks for eases partition handling.

  • usePartition: retrieves current partition state.
import { usePartition } from '@calvear/react-redux/hooks';
import { SamplePartition } from 'store/sample';

export default function MainPage()
{
    const { state, data, error } = usePartition(SamplePartition);

    ...
}
  • useActionDispatch: returns an action dispatcher.
import { useEffect } from 'react';
import { useActionDispatch } from '@calvear/react-redux/hooks';
import { SamplePartition } from 'store/sample';

export default function MainPage()
{
    const dispatchSampleExec = useActionDispatch(SamplePartition.Type.EXEC);

    useEffect(() =>
    {
        dispatchSampleExec({ someProp: 'hello world' });
    }, []);

    ...
}

Also, exports every hook from react-redux lib.

  • useSelector: extracts data from the Redux store state.
import { useSelector } from '@calvear/react-redux/hooks';
import { SamplePartition } from 'store/sample';

export default function MainPage()
{
    const state = useSelector(({ [SamplePartition.Key]: state }) => state);
    // in this example, will returns same that usePartition(...)

    ...
}
  • useDispatch: returns a store dispatcher.
import { useEffect } from 'react';
import { useDispatch } from '@calvear/react-redux/hooks';
import { SamplePartition } from 'store/sample';

export default function MainPage()
{
    const dispatch = useDispatch();

    useEffect(() =>
    {
        dispatch({
            type: SamplePartition.Type.EXEC,
            payload: { someProp: 'hello world' }
        });
        // in this example, will behaves like useActionDispatch(...)
    }, []);
    ...
}
  • useStore: returns a reference to the same Redux store.
import { useStore } from '@calvear/react-redux/hooks';

export default function MainPage()
{
    const store = useStore();

    const state = store.getState();

    ...
}

reselect

Library integrates and exports reselect lib.

  • createSelector: creates a memoized selector.
import { createPartitionSelector } from '@calvear/react-redux';
import { useSelector } from '@calvear/react-redux/hooks';
import { createSelector } from '@calvear/react-redux/reselect';
import { SamplePartition } from 'store/sample';

const sampleSelector = createPartitionSelector(SamplePartition);

const sampleDataSelector = createSelector(
    sampleSelector,
    sample => sample.data
)

export default function MainPage()
{
    const data = useSelector(sampleDataSelector);

    ...
}

Saga Effects

Library has custom redux-saga effects.

  • dispatch: dispatches an action with optional payload.
import { dispatch } from '@calvear/react-redux/effects';
import { SamplePartition } from 'store/sample';

function* exec({ payload })
{
    // dispatches COMMIT action
    yield dispatch(
        SamplePartition.Type.COMMIT,
        payload
    );
}

...
  • selectPartition: extracts partition from Redux state.
import { selectPartition } from '@calvear/react-redux/effects';
import { SamplePartition } from 'store/sample';

function* exec()
{
    // extracts sample state from store
    const { state, data, error } = yield selectPartition(SamplePartition);
}

...
  • takeAny: waits for any action type to occur n times.
import { takeAny } from '@calvear/react-redux/effects';
import { SamplePartition } from 'store/sample';

function* exec()
{
    // waits for any EXEC or COMMIT action,
    // intercepting two of these dispatches
    const [
        firstResult,
        secondResult
    ] = yield takeAny([
        SamplePartition.Type.EXEC,
        SamplePartition.Type.COMMIT
    ], 2);
}

...

Avoid actions types collision

Redux doesn't handles action types collision for reducers, so, for example if we has two partitions/reducers with same actions (EXEC, COMMIT), will conflicts dispatching any of these.

This library provides of a utility for prefix partition key to every action type.

import { packagePartitionHandler } from '@calvear/react-redux/utils';

let SamplePartition = {
    // partition key
    Key: 'SAMPLE',

    // action types
    Type: {
        EXEC: 'EXEC',
        COMMIT: 'COMMIT',
        ROLLBACK: 'ROLLBACK',
    },

    ...
};

// prefixes action types with partition key
export default packagePartitionHandler(SamplePartition);

Linting šŸ§æ

Project uses ESLint, for code formatting and code styling normalizing.

  • eslint: JavaScript and React linter with Airbnb React base config and some other additions.
  • prettier: optional Prettier config.

For correct interpretation of linters, is recommended to use Visual Studio Code as IDE and install the plugins in .vscode folder at 'extensions.json', as well as use the config provided in 'settings.json'

Changelog šŸ“„

For last changes see CHANGELOG.md file for details.

Built with šŸ› ļø

  • React - the most fabulous JavaScript framework.
  • Redux - most popular frontend state handler.
  • React Redux - perfect React Redux integration.
  • Redux Saga - powerfull Redux middleware.
  • Redux Logger - impressive logger for Redux actions.
  • reselect - redux memoized selectors library.

License šŸ“„

This project is licensed under the MIT License - see LICENSE.md file for details.


āŒØ by Alvear Candia, Cristopher Alejandro