Flexible binding between Vue and Redux, allowing use of multiple stores. It works, in the same way, like render props does in React. It uses Scoped Slot - read my article about it.
Note: The previous version was using Higher Order Components (HOC); this version uses Scoped slots instead. No more magic with the connect methods. Everything is explicit which will prevent props collision and an ugly trick with the render function.
Why you should use it:
- Just 45 lines of code.
- No dependencies at all
- Easy to read, understand, and extend.
- Same API as react-redux.
- Combine multiple Providers to be populated by multiple sources.
- No hard coded dependencies between 'Vue' and the store, so more composable.
- Doesn't polluate
data
, so you can use the power of thefunctional component
- Debuggable in the Vue devtool browser extension.
- Elegant JSX syntax.
Created by gh-md-toc
npm install --save vuejs-redux
Let's build a counter app. The full code can be found in the example/
directory.
Start with a reducer:
export function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
case 'RESET':
return 0
default:
return state
}
}
Create the action creators in order to update our state:
export function increment() {
return { type: 'INCREMENT' }
}
export function decrement() {
return { type: 'DECREMENT' }
}
export function reset() {
return { type: 'RESET' }
}
We can now create the CounterProvider component. It acts as a Provider for our CounterComponent:
<template>
<Provider
:mapDispatchToProps="mapDispatchToProps"
:mapStateToProps="mapStateToProps"
:store="store"
>
<template #default="{ counterValue, actions }">
<!-- the provider calls the default slot with the result of mapStateToProps and mapDispatchToProps -->
<Counter :counterValue="counterValue" :actions="actions" :title="title" />
<!-- explicitly pass other props (ex: title) -->
</template>
</Provider>
</template>
<script>
import Provider from 'vuejs-redux'
import { createStore, bindActionCreators } from 'redux'
import { counter } from '../Reducers/Counter'
import * as Actions from '../Actions'
import Counter from './Counter.vue'
export default {
methods: {
mapStateToProps(state) {
return { counterValue: state }
},
mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(Actions, dispatch) }
},
},
components: {
Counter,
Provider,
},
data: () => ({
store: createStore(counter),
title: 'Counter using vuejs-redux',
}),
}
</script>
And finally our Counter component:
<template functional>
<!-- we can use functional component -->
<div>
<h1>Counter using vuejs-redux</h1>
<div>{{ counterValue }}</div>
<button @click="actions.increment()">increment</button>
<button @click="actions.decrement()">decrement</button>
<button @click="actions.reset()">reset</button>
</div>
</template>
<script>
export default {
props: ['actions', 'counterValue'],
}
</script>
The Counter component is not aware that we are using redux.
If you use JSX, you can use the same syntax as React render props:
render(h) {
return (
<Provider mapDispatchToProps={this.mapDispatchToProps} mapStateToProps={this.mapStateToProps} store={this.store}>
{({actions, counterValue}) => (
<Counter counterValue={counterValue} actions={actions} title={this.title} />
)}
</Provider>
);
},
You can combine multiple store if needed, use the Provider component various times. You can obviously create an helper component or whatever to compose this.
<template>
<Provider
:store=storeOne
:mapStateToProps=mapStateToPropsOne
:mapDispatchToProps=mapDispatchToPropsOne>
<template #default="{ myStateOne, myActionOne }">
<!-- Use our second provider -->
<Provider
:store=storeTwo
:mapStateToProps=mapStateToPropsTwo
:mapDispatchToProps=mapDispatchToPropsTwo>
<template #default="{ myStateTwo, myActionTwo }">
<!-- render our component here -->
<Child :stateOne=myStateOne :stateTwo=myStateTwo />
</template>
</Provider>
</template>
</Provider
</template>
Importing the store and passing it to every Provider can be a pain point. Hopefully,
we can create a custom provider that receive mapStateToProps
and mapDispatchToProps
as props,
imports the store, and call the vuejs-redux provider with the right parameters.
Here is an example:
CustomProvider.vue
<template>
<Provider
:mapDispatchToProps="mapDispatchToProps"
:mapStateToProps="mapStateToProps"
:store="store"
>
<template #default="props">
<!--Retrieve the data from the provider -->
<slot v-bind="props"></slot>
<!-- forward the data to the scoped-slot -->
</template>
</Provider>
</template>
<script>
import store from '../configure-store'
import Provider from 'vuejs-redux'
export default {
props: ['mapDispatchToProps', 'mapStateToProps'],
components: {
Provider,
},
data: () => ({
store,
}),
}
</script>
Checkout the working example
Sometimes, the examples in the repo are more up-to-date than the example in codesandbox. You can open an issue if you find a broken codesandbox example.
To run the example locally, you need to switch to the example directory:
cd ./examples/counter
Install the dependencies with:
npm install # (or yarn)
Either build the example and serve the dist
directory or start the dev mode (with hot reloading)
# Start the development mode
npm start
# Build
npm run build
Unit testing is done with jest. To run the test:
npm test
This plugin is compatible with rematch: live example
-
Counter: https://codesandbox.io/s/l9o83q28m
-
Counter (jsx): https://codesandbox.io/s/konq1nzjxv
Feel free to create issues or pull requests if needed.