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.