Skip to content

titouancreach/vuejs-redux

Repository files navigation

vuejs-redux

npm version Build Status GitHub contributors Say Thanks!

Description

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 the functional component
  • Debuggable in the Vue devtool browser extension.
  • Elegant JSX syntax.

Table of Contents

Created by gh-md-toc

Install

npm install --save vuejs-redux

Counter example

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>
    );
  },

Multiple stores

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>

Avoid passing the store to every <Provider ...>

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

Running the examples locally

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

Testing

Unit testing is done with jest. To run the test:

npm test

Rematch

This plugin is compatible with rematch: live example

Live examples

CONTRIBUTING

Feel free to create issues or pull requests if needed.