@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
Markdown
# 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)
```
```