Skip to content

Commit

Permalink
Added connect, updated readme, changed version to 2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukasz-pluszczewski committed Jan 21, 2018
1 parent 3889c60 commit 037e587
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 50 deletions.
1 change: 1 addition & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ All contributions are appreciated!
* [Default plugin usage](docs/defaultPlugin.md)
* [API](docs/api.md)
* [Writing your own plugins](docs/writingPlugin.md)
* [Changelog](docs/changelog.md)

## Ok, so what do I get?
Let's imagine you created forms. A lot of forms. And you wanted to keep values in redux state.
Expand Down
24 changes: 7 additions & 17 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,24 @@
- **actionName**: *string* name of the action (must be the same as key of the action definition)
- returns: **actionCreator**

#### `tools.chainReducers(...reducer)`
- `import { tools } from 'redux-breeze'`
#### `chainReducers(...reducer)`
- `import { chainReducers } from 'redux-breeze'`
- helper function to chain several reducers (and e.g. assign it to same field in redux state)
- when called chained reducer, all chained reducers will be called in order, each getting state returned by previous one
- arguments
- **reducer**: *function* reducers to chain
- return **chainedReducer**

#### `tools.createActionType(actionName, suffix, prefix)`
- `import { tools } from 'redux-breeze'`
#### `createActionType(actionName, suffix, prefix)`
- `import { createActionType } from 'redux-breeze'`
- helper function to create redux action type from actionName
- arguments:
- **actionName**: *string*
- **suffix**: *string* (default: **''**) string that will be added at the end of the created action type
- **prefix**: *string* (default: **''**) string that will be added at the beginning of the created action type
- example:
```javascript
tools.createActionType('myFancyName'); // MY_FANCY_NAME
tools.createActionType('myFancyName', 'success'); // MY_FANCY_NAME_SUCCESS
tools.createActionType('myFancyName', '', 'blah'); // BLAH_MY_FANCY_NAME
createActionType('myFancyName'); // MY_FANCY_NAME
createActionType('myFancyName', 'success'); // MY_FANCY_NAME_SUCCESS
createActionType('myFancyName', '', 'blah'); // BLAH_MY_FANCY_NAME
```

#### `tools.immutableSet(object, path, value, delimiter)`
- `import { tools } from 'redux-breeze'`
- works like lodash's _.set() but does not mutate the object (can be used to easily, immutably set value in complicated nested structure)
- arguments:
- **object**: *object* object to set value in
- **path**: *string|assignmentsObject}* path to the value you want to change, can be deep where field values are divided by *delimiter* (default: .) e.g. 'field.subField.somethingElse'. Can be an object e.g. { 'field.subField': 'newValue', 'anotherField.anotherSubField': 'anotherNewValue' } Lodash's array like syntax is not supported (e.g. 'field\[1\].subField')
- **value**: *any* value to be set in the path (if path is an object *value* is ignored)
- **delimiter**: *string* (defaut: **'.'**) delimiter used in the path
- returns **newObject**
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#### 1.0.0
- Initial release

#### 2.0.0
- Added "connect" function
- All tools are now named exports
34 changes: 16 additions & 18 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# reduxBreeze
# redux-breeze
>Powerful redux wrapper to make handling redux actions and reducers a breeze!
[![CircleCI](https://circleci.com/gh/Lukasz-pluszczewski/reduxBreeze.svg?style=svg)](https://circleci.com/gh/Lukasz-pluszczewski/reduxBreeze)
Expand Down Expand Up @@ -26,11 +26,12 @@ There are at least 3 options:
All contributions are appreciated!

## Documentation
* [Getting started](gettingStarted.md)
* [Glossary](glossary.md)
* [Default plugin usage](defaultPlugin.md)
* [API](api.md)
* [Writing your own plugins](writingPlugin.md)
* [Getting started](docs/gettingStarted.md)
* [Glossary](docs/glossary.md)
* [Default plugin usage](docs/defaultPlugin.md)
* [API](docs/api.md)
* [Writing your own plugins](docs/writingPlugin.md)
* [Changelog](docs/changelog.md)

## Ok, so what do I get?
Let's imagine you created forms. A lot of forms. And you wanted to keep values in redux state.
Expand Down Expand Up @@ -62,18 +63,18 @@ Only define something like:
```javascript
changeFullName: {
type: 'default',
result: [
{ sourcePath: 'payload', targetPath: 'fullName' }
],
result: {
fullName: 'payload',
},
}
```
And that's all! No action creators written. No reducer cases! You want default values? Or maybe custom initial value?
```javascript
changeFullName: {
type: 'default',
result: [
{ sourcePath: 'payload', targetPath: 'fullName', defaultValue: 'John Doe', initialValue: 'No full name here yet' }
],
result: {
fullName: { source: 'payload', default: 'John Doe', initial: 'No full name here yet' },
},
}
```
And then use it like this:
Expand All @@ -95,6 +96,9 @@ connect(
Interested?
Dive into the documentation :)
## Plugins
- [redux-better-promise](https://github.com/Lukasz-pluszczewski/redux-breeze-plugin-better-promise)
## I wanna help!
Great!
Expand All @@ -103,14 +107,8 @@ All pull requests are appreciated as long as the code is well tested, documented
You may grab a thing from todo (see below) or write plugins for your use-case.
## Todo
* make readme great again:
* document config (not a big thing ;) )
* add basic usage tutorial
* add plugins tutorial
* add tests to default plugin
* write action definitions validation (by default only checking for 'type' field, but make it plugin enabled)
* add more plugins:
* redux-saga
* redux-thunk
* redux-better-promise
* add selectors functionality (plugin-enabled)
2 changes: 1 addition & 1 deletion docs/writingPlugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#### `plugin(tools, config)`
- parameters
- **tools**: *object* object of tools (consist of *chainReducer*, *createActionType*, *immutableSet*)
- **tools**: *object* object of tools (consist of *chainReducer*, *createActionType*)
- **config**: *object* reduxBreeze config object
- returns **pluginObject**

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "redux-breeze",
"version": "1.0.0",
"version": "2.0.0",
"description": "",
"repository": {
"type": "git",
Expand Down Expand Up @@ -48,6 +48,7 @@
"eslint-watch": "3.1.2",
"mocha": "3.5.0",
"nyc": "11.4.1",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"rimraf": "2.6.1",
"rollup": "0.49.2",
Expand All @@ -60,6 +61,7 @@
"perfect-immutable": "1.3.0"
},
"peerDependencies": {
"redux": "^3.7.2"
"react-redux": "^5.x.x",
"redux": "^3.x.x"
}
}
18 changes: 14 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { set } from 'perfect-immutable';
import {
createActionType,
chainReducers,
connect,
mergePlugins,
checkConflicts,
} from './tools';
import createDefaultPlugin from './defaultPlugin';
import defaultPlugin from './defaultPlugin';

const defaultConfig = {
useDefaultPlugin: true,
Expand All @@ -24,14 +25,23 @@ const defaultConfig = {
},
};

export const tools = {
export {
createActionType,
chainReducers,
connect,
mergePlugins,
checkConflicts,
defaultPlugin,
};

export const defaultPlugin = createDefaultPlugin;
// still exported for backwards compatibility and testing purposes
export const tools = {
createActionType,
chainReducers,
connect,
mergePlugins,
checkConflicts,
};

const createReduxBreezeInstance = (actionDefinitions, userConfig = defaultConfig, ...plugins) => {
const config = {
Expand All @@ -41,7 +51,7 @@ const createReduxBreezeInstance = (actionDefinitions, userConfig = defaultConfig

// merging plugins
const pluginsToMerge = config.useDefaultPlugin
? [createDefaultPlugin(tools), ...plugins.map(plugin => plugin(tools, config))]
? [defaultPlugin(tools), ...plugins.map(plugin => plugin(tools, config))]
: plugins.map(plugin => plugin(tools, config));

const plugin = mergePlugins(
Expand Down
29 changes: 29 additions & 0 deletions src/tools.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'lodash';
import { connect as reduxConnect } from 'react-redux';

/**
* Helper for redux to attach reducers to one field instead of composing them into separate fields
Expand All @@ -18,6 +19,34 @@ export const chainReducers = rawReducers => (state, action) => {
return reducers.reduce((accuState, reducer) => reducer(accuState, action), state);
};

/**
* Converts object of paths to traditional mapStateToProps function
* @param {object|function} mapState object of paths or traditional mapStateToProps function
* @return {function} mapStateToProps function
*/
export const getNewMapState = mapState => {
if (_.isPlainObject(mapState)) {
return state => _.mapValues(mapState, (value, key) => {
if (_.isString(value)) {
return _.get(state, value.replace(/^state\./, ''));
}
if (_.isFunction(value)) {
return value(state);
}
throw new Error(`When using plain object in "connect", values must be either strings (paths to values in state) or functions (selectors). Check value in ${key} field`);
});
}
return mapState;
};

/**
* Works like react-redux connect but allows you to use object of paths as first argument
* @param {object|function} mapState objects of paths or traditional mapStateToProps function
* @param {array} rest rest of connect arguments
* @return {function} connect HOC
*/
export const connect = (mapState, ...rest) => reduxConnect(getNewMapState(mapState), ...rest);

/**
* Checks if there are conflicts in plugins in given adapterType (conflict = two plugins handling same actionType)
* @param {array} plugins list of plugins
Expand Down
67 changes: 59 additions & 8 deletions test/reduxBreeze.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { set } from 'perfect-immutable';

import createReduxBreezeInstance, { tools, defaultPlugin as createDefaultPlugin } from '../src/index';
import createReduxBreezeInstance, {
tools,
checkConflicts,
defaultPlugin as createDefaultPlugin,
mergePlugins,
} from '../src/index';

import { getNewMapState } from '../src/tools';

chai.use(chaiAsPromised);
chai.use(sinonChai);
Expand Down Expand Up @@ -357,7 +364,7 @@ describe('reduxBreeze', () => {
describe('tools', () => {
describe('checkConflicts', () => {
it('should return empty string when no conflicts were found', () => {
const testResult = tools.checkConflicts(
const testResult = checkConflicts(
[
{ name: 'plugin1', actionAdapter: { test1() {} } },
{ name: 'plugin2', actionAdapter: { test2() {} } },
Expand All @@ -368,7 +375,7 @@ describe('reduxBreeze', () => {
expect(testResult).to.be.equal('');
});
it('should return non empty string when conflicts were found', () => {
const testResult = tools.checkConflicts(
const testResult = checkConflicts(
[
{ name: 'plugin1', actionAdapter: { test1() {} } },
{ name: 'plugin2', actionAdapter: { test1() {} } },
Expand All @@ -381,7 +388,7 @@ describe('reduxBreeze', () => {
});
it('should use provided mpaActionTypes function to map actionTypes', () => {
const mapActionTypes = sinon.spy((actionType, pluginName, adapterName) => pluginName);
const testResult = tools.checkConflicts(
const testResult = checkConflicts(
[
{ name: 'test1', actionAdapter: { test1() {} } },
{ name: 'test2', actionAdapter: { test1() {} } },
Expand Down Expand Up @@ -425,7 +432,7 @@ describe('reduxBreeze', () => {
test3: sinon.spy(),
},
};
const mergedPlugins = tools.mergePlugins([plugin1, plugin2]);
const mergedPlugins = mergePlugins([plugin1, plugin2]);

expect(mergedPlugins).to.nested.include({
'actionAdapter.test1': plugin1.actionAdapter.test1,
Expand Down Expand Up @@ -464,7 +471,7 @@ describe('reduxBreeze', () => {
test1: sinon.spy(),
},
};
expect(() => tools.mergePlugins([plugin1, plugin2])).to.throw(Error);
expect(() => mergePlugins([plugin1, plugin2])).to.throw(Error);
});
it('should not throw an error when there are conflicts but strict mode is turned off', () => {
const plugin1 = {
Expand All @@ -491,7 +498,7 @@ describe('reduxBreeze', () => {
test1: sinon.spy(),
},
};
expect(() => tools.mergePlugins([plugin1, plugin2], { strict: false })).to.not.throw(Error);
expect(() => mergePlugins([plugin1, plugin2], { strict: false })).to.not.throw(Error);
});
it('should map actionNames', () => {
const plugin1 = {
Expand Down Expand Up @@ -521,7 +528,7 @@ describe('reduxBreeze', () => {
const mapActionTypes = sinon.spy((actionType, pluginName, adapterType) => pluginName + actionType);
let mergedPlugins = {};

expect(() => mergedPlugins = tools.mergePlugins([plugin1, plugin2], { mapActionTypes })).to.not.throw(Error);
expect(() => mergedPlugins = mergePlugins([plugin1, plugin2], { mapActionTypes })).to.not.throw(Error);

expect(mapActionTypes).to.have.been.callCount(12);
expect(mapActionTypes).to.have.been.calledWith('test1', 'plugin1', 'actionAdapter');
Expand All @@ -541,6 +548,50 @@ describe('reduxBreeze', () => {
});
});
});
describe('getNewMapState', () => {
it('should create map function out of plain object', () => {
const mapState = {
foo: 'state.foo.value',
bar: 'bar.value',
noValue: 'no.value.here',
baz: state => state.baz.value,
};

const state = {
foo: {
value: 'fooValue',
},
bar: {
value: 'barValue',
},
noValue: {},
baz: {
value: 'bazValue',
},
};

expect(getNewMapState(mapState)(state)).to.be.deep.equal({
foo: 'fooValue',
bar: 'barValue',
noValue: undefined,
baz: 'bazValue',
});
});

it('should return map function without altering it', () => {
const mapState = state => ({ foo: state.foo.value });

const state = {
foo: {
value: 'fooValue',
},
};

expect(getNewMapState(mapState)(state)).to.be.deep.equal({
foo: 'fooValue',
});
});
});
});

describe('defaultPlugin', () => {
Expand Down

0 comments on commit 037e587

Please sign in to comment.