redux-vue 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.babelrc CHANGED
@@ -1,3 +1,4 @@
1
1
  {
2
- "presets": ["es2015","stage-0"]
3
- }
2
+ "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]],
3
+ "plugins": ["@babel/plugin-proposal-export-default-from"]
4
+ }
package/.editorconfig CHANGED
@@ -1,9 +1,15 @@
1
+ # EditorConfig — https://editorconfig.org
1
2
  root = true
2
3
 
3
4
  [*]
4
5
  charset = utf-8
5
- indent_style = space
6
- indent_size = 2
7
6
  end_of_line = lf
8
7
  insert_final_newline = true
9
8
  trim_trailing_whitespace = true
9
+
10
+ [*.{js,json,md,yml,yaml}]
11
+ indent_style = space
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something broken? Let us know.
4
+ labels: bug
5
+ ---
6
+
7
+ **redux-vue version:**
8
+ **Vue version:**
9
+ **redux version:**
10
+
11
+ **Steps to reproduce:**
12
+
13
+ **Expected behaviour:**
14
+
15
+ **Actual behaviour:**
16
+
17
+ **Minimal reproduction (snippet or repo link):**
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an improvement or new behaviour.
4
+ labels: enhancement
5
+ ---
6
+
7
+ **Problem this would solve:**
8
+
9
+ **Proposed solution:**
10
+
11
+ **Alternatives considered:**
@@ -0,0 +1,8 @@
1
+ ## Description
2
+
3
+ ## Checklist
4
+
5
+ - [ ] Tests added or updated
6
+ - [ ] `npm test` passes locally
7
+ - [ ] README updated if API changed
8
+ - [ ] No secrets or hardcoded URLs introduced
@@ -0,0 +1,23 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ node-version: [18, 20]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Use Node.js ${{ matrix.node-version }}
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: ${{ matrix.node-version }}
21
+ cache: npm
22
+ - run: npm ci
23
+ - run: npm test
@@ -0,0 +1,26 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity, level of experience, nationality, personal appearance, race, religion, or sexual identity.
6
+
7
+ ## Our Standards
8
+
9
+ Positive behaviour includes:
10
+ - Using welcoming and inclusive language
11
+ - Being respectful of differing viewpoints
12
+ - Gracefully accepting constructive criticism
13
+ - Focusing on what is best for the community
14
+
15
+ Unacceptable behaviour includes:
16
+ - Harassment, trolling, or personal attacks
17
+ - Publishing others' private information without permission
18
+ - Other conduct which could reasonably be considered inappropriate
19
+
20
+ ## Enforcement
21
+
22
+ Project maintainers may remove, edit, or reject comments, commits, code, issues, and other contributions not aligned with this Code of Conduct.
23
+
24
+ ## Attribution
25
+
26
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
@@ -0,0 +1,35 @@
1
+ # Contributing to redux-vue
2
+
3
+ Thanks for taking the time to contribute!
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/nadimtuhin/redux-vue.git
9
+ cd redux-vue
10
+ npm install
11
+ npm test
12
+ ```
13
+
14
+ ## Development Workflow
15
+
16
+ 1. Fork the repo and create a branch: `git checkout -b fix/my-fix`
17
+ 2. Write a failing test first (TDD — see `src/connect.spec.js` for examples)
18
+ 3. Implement the fix / feature
19
+ 4. Ensure all tests pass: `npm test`
20
+ 5. Submit a pull request against `master`
21
+
22
+ ## Pull Request Guidelines
23
+
24
+ - One concern per PR
25
+ - Include tests for any behaviour change
26
+ - Update README if you add a new API argument
27
+ - Keep commits conventional: `fix:`, `feat:`, `docs:`, `test:`
28
+
29
+ ## Reporting Bugs
30
+
31
+ Use the GitHub issue tracker. Include:
32
+ - redux-vue version
33
+ - Vue version
34
+ - Minimal reproduction (snippet or repo link)
35
+ - Expected vs actual behaviour
package/README.md CHANGED
@@ -1,110 +1,184 @@
1
- # vue redux binding higher order component
2
- Vue Redux is tested to work on vue v2 and should be used with vue-jsx or in component template string. For more on vue-jsx https://github.com/vuejs/babel-plugin-transform-vue-jsx
1
+ # redux-vue
2
+
3
+ [![CI](https://github.com/nadimtuhin/redux-vue/actions/workflows/ci.yml/badge.svg)](https://github.com/nadimtuhin/redux-vue/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/redux-vue.svg)](https://www.npmjs.com/package/redux-vue)
5
+ [![npm downloads](https://img.shields.io/npm/dm/redux-vue.svg)](https://www.npmjs.com/package/redux-vue)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ Redux bindings for **Vue 2** — a higher-order component (HOC) that connects Vue
9
+ components to a Redux store, similar to `react-redux`'s `connect()`.
10
+
11
+ > **Vue 2 only.** This library targets Vue 2.x. For Vue 3, use Pinia or `vuex` 4.x.
12
+
13
+ ---
3
14
 
4
15
  ## Install
5
- install through ``npm i redux-vue --save``
6
16
 
7
- ## Initialize
8
- install in your root component
17
+ ```bash
18
+ npm install redux-vue
19
+ # or
20
+ yarn add redux-vue
21
+ ```
22
+
23
+ ## Setup
24
+
25
+ Register the plugin on your root Vue instance so all child components can access
26
+ the store:
9
27
 
10
28
  ```js
11
29
  // main.js
12
30
  import Vue from 'vue';
13
31
  import { reduxStorePlugin } from 'redux-vue';
14
- import AppStore from './AppStore';
15
- import App from './Component/App';
32
+ import store from './store';
33
+ import App from './App';
16
34
 
17
- // install redux-vue
18
35
  Vue.use(reduxStorePlugin);
19
36
 
20
37
  new Vue({
21
- store: AppStore,
22
- render(h) {
23
- return <App />
24
- }
38
+ store,
39
+ render: h => h(App),
25
40
  });
26
41
  ```
27
42
 
43
+ ## Usage
44
+
45
+ ### `connect(mapStateToProps, mapDispatchToProps, [mergeProps])(Component)`
46
+
47
+ Wraps a Vue component and injects store state and dispatch functions as props.
48
+
28
49
  ```js
29
- // store.js
30
- import { createStore } from 'redux';
50
+ // components/TodoApp.js
51
+ import { connect } from 'redux-vue';
31
52
 
32
- const initialState = {
33
- todos: []
53
+ const TodoApp = {
54
+ props: {
55
+ todos: { type: Array },
56
+ addTodo: { type: Function },
57
+ },
58
+ render(h) {
59
+ return (
60
+ <div>
61
+ <ul>{this.todos.map(t => <li>{t}</li>)}</ul>
62
+ <button onClick={() => this.addTodo('new item')}>Add</button>
63
+ </div>
64
+ );
65
+ },
34
66
  };
35
67
 
36
- const reducer = (state = initialState, action) => {
37
- switch(action.type){
38
- case 'ADD_TODO':
39
- return {
40
- ...state,
41
- todos: [...state.todos, action.data.todo]
42
- }
68
+ const mapStateToProps = state => ({ todos: state.todos });
43
69
 
44
- default:
45
- return state;
46
- }
47
- }
48
-
49
- const AppStore = createStore(reducer);
70
+ const mapDispatchToProps = dispatch => ({
71
+ addTodo: todo => dispatch({ type: 'ADD_TODO', payload: todo }),
72
+ });
50
73
 
51
- export default AppStore;
74
+ export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
52
75
  ```
53
76
 
54
- ## Use in your component
77
+ ### Single-file components
78
+
55
79
  ```js
56
80
  // components/App.js
57
-
58
81
  import { connect } from 'redux-vue';
82
+ import Comp from './Comp.vue';
83
+
84
+ const mapStateToProps = state => ({ count: state.count });
85
+ const mapDispatchToProps = dispatch => ({
86
+ increment: () => dispatch({ type: 'INCREMENT' }),
87
+ });
88
+
89
+ export default connect(mapStateToProps, mapDispatchToProps)(Comp);
90
+ ```
91
+
92
+ ---
93
+
94
+ ## API
95
+
96
+ ### `connect([mapStateToProps], [mapDispatchToProps], [mergeProps])(Component)`
97
+
98
+ | Argument | Type | Description |
99
+ | --------------------------------------- | --------------------- | -------------------------------------------------------- |
100
+ | `mapStateToProps(state, ownAttrs)` | Function | Maps store state to props. Called on every store update. |
101
+ | `mapDispatchToProps(dispatch)` | Function | Maps dispatch calls to props. |
102
+ | `mergeProps(stateProps, dispatchProps)` | Function _(optional)_ | Combine or rename keys before they reach the child. |
103
+
104
+ **Pass-through props** — props not declared in the map functions are forwarded to
105
+ the wrapped component automatically.
106
+
107
+ **Slots** — slot content on the connected wrapper is forwarded to the inner component.
108
+
109
+ ### `reduxStorePlugin`
110
+
111
+ A Vue plugin. Call `Vue.use(reduxStorePlugin)` once with `store` set on the root
112
+ instance. All descendant components will have `this.$store` available.
59
113
 
60
- const App = {
61
- props: {
62
- todos: {
63
- type: Array,
64
- },
65
- addTodo: {
66
- type: Function,
67
- },
68
- },
69
-
70
- methods: {
71
- handleAddTodo() {
72
- const todo = this.$refs.input.value;
73
- this.addTodo(todo);
74
- }
75
- },
76
-
77
- render(h) {
78
- return <div>
79
- <ul>
80
- {this.todos.map(todo => <li>{todo}</li>)}
81
- </ul>
82
-
83
- <div>
84
- <input type="text" ref="input" />
85
- <button on-click={this.handleAddTodo}>add todo</button>
86
- </div>
87
- </div>
88
- }
114
+ ---
115
+
116
+ ## Examples
117
+
118
+ ### `mergeProps`
119
+
120
+ ```js
121
+ const mergeProps = (stateProps, dispatchProps) => ({
122
+ fullCount: stateProps.count * 2,
123
+ onAdd: dispatchProps.addTodo,
124
+ });
125
+
126
+ export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(App);
127
+ ```
128
+
129
+ ### Pass-through props
130
+
131
+ ```html
132
+ <!-- nonMappedProp is not in mapStateToProps — it still reaches the wrapped component -->
133
+ <ConnectedApp nonMappedProp="Foo" />
134
+ ```
135
+
136
+ ### Slots
137
+
138
+ ```html
139
+ <ConnectedApp>
140
+ <span>This slot content is forwarded to the inner component</span>
141
+ </ConnectedApp>
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Store example
147
+
148
+ ```js
149
+ // store.js
150
+ import { createStore } from 'redux';
151
+
152
+ const initialState = { todos: [] };
153
+
154
+ const reducer = (state = initialState, action) => {
155
+ switch (action.type) {
156
+ case 'ADD_TODO':
157
+ return { ...state, todos: [...state.todos, action.payload] };
158
+ default:
159
+ return state;
160
+ }
89
161
  };
90
162
 
91
- function mapStateToProps(state) {
92
- return {
93
- todos: state.todos
94
- };
95
- }
96
-
97
- function mapActionToProps(dispatch) {
98
- return {
99
- addTodo(todo) {
100
- dispatch({
101
- type: 'ADD_TODO',
102
- data: { todo }
103
- })
104
- }
105
- }
106
- }
107
-
108
- export default connect(mapStateToProps, mapActionToProps)(App);
163
+ export default createStore(reducer);
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Development
109
169
 
170
+ ```bash
171
+ git clone https://github.com/nadimtuhin/redux-vue.git
172
+ cd redux-vue
173
+ npm install
174
+ npm test
110
175
  ```
176
+
177
+ Tests live in `src/*.spec.js` and use Mocha. See [CONTRIBUTING.md](CONTRIBUTING.md)
178
+ for contribution guidelines.
179
+
180
+ ---
181
+
182
+ ## License
183
+
184
+ [MIT](LICENSE) © Nadim Tuhin
package/lib/connect.js CHANGED
@@ -1,120 +1,126 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
-
7
- var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8
-
9
- exports.default = connect;
10
-
11
- var _normalizeProps = require('./normalizeProps');
12
-
13
- var _normalizeProps2 = _interopRequireDefault(_normalizeProps);
14
-
15
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
-
1
+ import normalizeProps from './normalizeProps';
17
2
  function noop() {}
18
-
19
3
  function getStore(component) {
20
4
  return component.$store;
21
5
  }
22
-
23
6
  function getAttrs(component) {
24
- return component._self.$options._parentVnode.data.attrs;
7
+ // issue #6: guard against missing attrs (root component has no _parentVnode attrs)
8
+ const vnode = component._self.$options._parentVnode;
9
+ return vnode && vnode.data && vnode.data.attrs || {};
25
10
  }
26
-
27
11
  function getStates(component, mapStateToProps) {
28
- var store = getStore(component);
29
- var attrs = getAttrs(component);
30
-
12
+ const store = getStore(component);
13
+ const attrs = getAttrs(component);
31
14
  return mapStateToProps(store.getState(), attrs) || {};
32
15
  }
33
-
34
16
  function getActions(component, mapActionsToProps) {
35
- var store = getStore(component);
36
-
17
+ const store = getStore(component);
37
18
  return mapActionsToProps(store.dispatch, getAttrs.bind(null, component)) || {};
38
19
  }
39
-
40
20
  function getProps(component) {
41
- var props = {};
42
- var attrs = getAttrs(component);
43
- var stateNames = component.vuaReduxStateNames;
44
- var actionNames = component.vuaReduxActionNames;
45
-
46
- for (var ii = 0; ii < stateNames.length; ii++) {
21
+ const attrs = getAttrs(component);
22
+ const stateNames = component.vuaReduxStateNames;
23
+ const actionNames = component.vuaReduxActionNames;
24
+ const mergedNames = component.vuaReduxMergedNames;
25
+
26
+ // mergeProps path: only expose merged keys
27
+ if (mergedNames) {
28
+ const props = {};
29
+ for (let ii = 0; ii < mergedNames.length; ii++) {
30
+ props[mergedNames[ii]] = component[mergedNames[ii]];
31
+ }
32
+ return {
33
+ ...props,
34
+ ...attrs
35
+ };
36
+ }
37
+ const props = {};
38
+ for (let ii = 0; ii < stateNames.length; ii++) {
47
39
  props[stateNames[ii]] = component[stateNames[ii]];
48
40
  }
49
-
50
- for (var _ii = 0; _ii < actionNames.length; _ii++) {
51
- props[actionNames[_ii]] = component[actionNames[_ii]];
41
+ for (let ii = 0; ii < actionNames.length; ii++) {
42
+ props[actionNames[ii]] = component[actionNames[ii]];
52
43
  }
53
-
54
- return _extends({}, props, attrs);
44
+ return {
45
+ ...props,
46
+ ...attrs
47
+ };
55
48
  }
56
49
 
57
- /**
58
- * 1. utilities are moved above because vue stores methods, states and props
59
- * in the same namespace
60
- * 2. actions are set while created
61
- */
62
-
63
50
  /**
64
51
  * @param mapStateToProps
65
52
  * @param mapActionsToProps
66
- * @returns Object
53
+ * @param mergeProps - optional; receives (stateProps, dispatchProps) -> props
54
+ * @returns Function (children) => VueComponentDescriptor
67
55
  */
68
- function connect(mapStateToProps, mapActionsToProps) {
56
+ export default function connect(mapStateToProps, mapActionsToProps, mergeProps) {
69
57
  mapStateToProps = mapStateToProps || noop;
70
58
  mapActionsToProps = mapActionsToProps || noop;
71
-
72
- return function (children) {
73
-
59
+ return children => {
74
60
  /** @namespace children.collect */
75
61
  if (children.collect) {
76
- children.props = _extends({}, (0, _normalizeProps2.default)(children.props || {}), (0, _normalizeProps2.default)(children.collect || {}));
77
-
78
- var msg = 'vua-redux: collect is deprecated, use props ' + ('in ' + (children.name || 'anonymous') + ' component');
79
-
62
+ children.props = {
63
+ ...normalizeProps(children.props || {}),
64
+ ...normalizeProps(children.collect || {})
65
+ };
66
+ const msg = `vua-redux: collect is deprecated, use props ` + `in ${children.name || 'anonymous'} component`;
80
67
  console.warn(msg);
81
68
  }
82
-
83
69
  return {
84
- name: 'ConnectVuaRedux-' + (children.name || 'children'),
85
-
86
- render: function render(h) {
87
- var props = getProps(this);
88
-
89
- return h(children, { props: props });
70
+ name: `ConnectVuaRedux-${children.name || 'children'}`,
71
+ render(h) {
72
+ const props = getProps(this);
73
+ // issue #3: forward $slots to child
74
+ return h(children, {
75
+ props,
76
+ slots: this.$slots
77
+ });
90
78
  },
91
- data: function data() {
92
- var state = getStates(this, mapStateToProps);
93
- var actions = getActions(this, mapActionsToProps);
94
- var stateNames = Object.keys(state);
95
- var actionNames = Object.keys(actions);
96
-
97
- return _extends({}, state, actions, {
79
+ data() {
80
+ const state = getStates(this, mapStateToProps);
81
+ const actions = getActions(this, mapActionsToProps);
82
+ if (mergeProps) {
83
+ // PR #5: mergeProps support
84
+ const merged = mergeProps(state, actions) || {};
85
+ const mergedNames = Object.keys(merged);
86
+ return {
87
+ ...merged,
88
+ vuaReduxMergedNames: mergedNames,
89
+ vuaReduxStateNames: [],
90
+ vuaReduxActionNames: []
91
+ };
92
+ }
93
+ const stateNames = Object.keys(state);
94
+ const actionNames = Object.keys(actions);
95
+ return {
96
+ ...state,
97
+ ...actions,
98
98
  vuaReduxStateNames: stateNames,
99
99
  vuaReduxActionNames: actionNames
100
- });
100
+ };
101
101
  },
102
- created: function created() {
103
- var _this = this;
104
-
105
- var store = getStore(this);
106
-
107
- this.vuaReduxUnsubscribe = store.subscribe(function () {
108
- var state = getStates(_this, mapStateToProps);
109
- var stateNames = Object.keys(state);
110
- _this.vuaReduxStateNames = stateNames;
111
-
112
- for (var ii = 0; ii < stateNames.length; ii++) {
113
- _this[stateNames[ii]] = state[stateNames[ii]];
102
+ created() {
103
+ const store = getStore(this);
104
+ this.vuaReduxUnsubscribe = store.subscribe(() => {
105
+ const state = getStates(this, mapStateToProps);
106
+ const actions = getActions(this, mapActionsToProps);
107
+ if (mergeProps) {
108
+ const merged = mergeProps(state, actions) || {};
109
+ const mergedNames = Object.keys(merged);
110
+ this.vuaReduxMergedNames = mergedNames;
111
+ for (let ii = 0; ii < mergedNames.length; ii++) {
112
+ this[mergedNames[ii]] = merged[mergedNames[ii]];
113
+ }
114
+ return;
115
+ }
116
+ const stateNames = Object.keys(state);
117
+ this.vuaReduxStateNames = stateNames;
118
+ for (let ii = 0; ii < stateNames.length; ii++) {
119
+ this[stateNames[ii]] = state[stateNames[ii]];
114
120
  }
115
121
  });
116
122
  },
117
- beforeDestroy: function beforeDestroy() {
123
+ beforeDestroy() {
118
124
  this.vuaReduxUnsubscribe();
119
125
  }
120
126
  };
package/lib/index.js CHANGED
@@ -1,19 +1,2 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.reduxStorePlugin = exports.connect = undefined;
7
-
8
- var _connect2 = require('./connect');
9
-
10
- var _connect3 = _interopRequireDefault(_connect2);
11
-
12
- var _reduxStorePlugin2 = require('./reduxStorePlugin');
13
-
14
- var _reduxStorePlugin3 = _interopRequireDefault(_reduxStorePlugin2);
15
-
16
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
-
18
- exports.connect = _connect3.default;
19
- exports.reduxStorePlugin = _reduxStorePlugin3.default;
1
+ export { default as connect } from './connect';
2
+ export { default as reduxStorePlugin } from './reduxStorePlugin';