UNPKG

@calvear/react-redux

Version:

Preconfigured Redux store initializer with Redux Saga, Redux Logger and Reselect for React SPA applications.

413 lines (312 loc) • 10.6 kB
# React Redux React library for eases Redux initialization for React SPA application. Is preconfigured and included helpful libraries as redux-logger and reselect. ## Structure šŸ“‹ ```bash ā”œā”€ā”€ 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: ```javascript 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: ```bash ā”œā”€ā”€ ... ā”œā”€ā”€ 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: ```javascript 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: ```javascript 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: ```javascript 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: ```javascript 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. ```javascript 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. ```javascript 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](https://react-redux.js.org/) lib. - **useSelector**: extracts data from the Redux store state. ```javascript 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. ```javascript 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. ```javascript import { useStore } from '@calvear/react-redux/hooks'; export default function MainPage() { const store = useStore(); const state = store.getState(); ... } ``` ### reselect Library integrates and exports [reselect](https://github.com/reduxjs/reselect) lib. - **createSelector**: creates a memoized selector. ```javascript 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](https://redux-saga.js.org/) effects. - **dispatch**: dispatches an action with optional payload. ```javascript 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. ```javascript 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. ```javascript 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. ```javascript 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](https://code.visualstudio.com/) 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](CHANGELOG.md) file for details. ## Built with šŸ› ļø - [React](https://reactjs.org/) - the most fabulous JavaScript framework. - [Redux](https://redux.js.org/) - most popular frontend state handler. - [React Redux](https://react-redux.js.org/) - perfect React Redux integration. - [Redux Saga](https://redux-saga.js.org/) - powerfull Redux middleware. - [Redux Logger](https://github.com/LogRocket/redux-logger) - impressive logger for Redux actions. - [reselect](https://github.com/reduxjs/reselect) - redux memoized selectors library. ## License šŸ“„ This project is licensed under the MIT License - see [LICENSE.md](LICENSE.md) file for details. --- ⌨ by [Alvear Candia, Cristopher Alejandro](https://github.com/calvear93) ``` ```