Skip to content

Commit

Permalink
Merge pull request #18 from declandewet/watcher-logic
Browse files Browse the repository at this point in the history
circumvent need to call refresh() after async actions
  • Loading branch information
Declan de Wet authored Nov 10, 2016
2 parents c20206d + 1c9f6d0 commit 471bfed
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 41 deletions.
32 changes: 11 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -681,38 +681,28 @@ Easy. Instead of defining `metaInfo` as an object, define it as a function and a

## How do I populate `metaInfo` from the result of an asynchronous action?

`vue-meta` exposes a method called `refresh` on the client-side that allows you to trigger an update at any given point in time.
`vue-meta` will do this for you automatically when your component state changes.

In the same way you access `$meta().inject()` on the server, you can access `$meta().refresh()`.

For example, if you're using Vuex and you have an action that fetches a `post` asynchronously, you should ensure that it returns a promise so that you are notified when the fetching is complete:
Just make sure that you're using the function form of `metaInfo`:

```js
{
actions: {
async fetchPost ({ commit }, payload) {
const post = yield db.fetch('posts', payload.postId)
commit('fetchedPost', post)
data () {
return {
title: 'Foo Bar Baz'
}
}
}
```

Then in your component, you can call `refresh()` to trigger an update once the fetch is complete:

```js
{
beforeMount () {
const postId = this.$router.params.id
this.$store.dispatch('fetchPost', { postId })
.then(() => this.$meta().refresh())
metaInfo () {
return {
title: this.title
}
}
}
```

Just make sure that whatever data source you're using (`store` if you're using Vuex, component `data` otherwise) has some sane defaults set so Vue doesn't complain about `null` property accessors.
Check out the [vuex-async](https://github.com/declandewet/vue-meta/tree/master/examples/vuex-async) example for a far more detailed demonstration if you have doubts.

Check out the [vuex-async](https://github.com/declandewet/vue-meta/tree/master/examples/vuex-async) example for a far more detailed demonstration of how this works.
Credit & Thanks for this feature goes to [Sébastien Chopin](https://github.com/Atinux).

# Examples

Expand Down
13 changes: 4 additions & 9 deletions examples/vuex-async/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,10 @@ export default new Vuex.Store({
actions: {
getPost ({ commit }, payload) {
commit('loadingState', { isLoading: true })
// we have to return a promise from this action so we know
// when it is finished
return new Promise((resolve) => {
setTimeout(() => {
commit('getPost', payload)
resolve()
}, 2000)
})
.then(() => commit('loadingState', { isLoading: false }))
setTimeout(() => {
commit('getPost', payload)
commit('loadingState', { isLoading: false })
}, 2000)
}
}
})
4 changes: 0 additions & 4 deletions examples/vuex-async/views/Post.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
name: 'post',
beforeMount () {
const { slug } = this.$route.params
// since fetching a post is asynchronous,
// we need to call `this.$meta().refresh()`
// to update the meta info
this.$store.dispatch('getPost', { slug })
.then(() => this.$meta().refresh())
},
computed: mapGetters([
'isLoading',
Expand Down
17 changes: 17 additions & 0 deletions src/client/batchUpdate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Performs a batched update. Uses requestAnimationFrame to prevent
* calling a function too many times in quick succession.
* You need to pass it an ID (which can initially be `null`),
* but be sure to overwrite that ID with the return value of batchUpdate.
*
* @param {(null|Number)} id - the ID of this update
* @param {Function} callback - the update to perform
* @return {Number} id - a new ID
*/
export default function batchUpdate (id, callback) {
window.cancelAnimationFrame(id)
return window.requestAnimationFrame(() => {
id = null
callback()
})
}
28 changes: 21 additions & 7 deletions src/shared/plugin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assign from 'object-assign'
import $meta from './$meta'
import batchUpdate from '../client/batchUpdate'

import {
VUE_META_KEY_NAME,
Expand Down Expand Up @@ -33,18 +34,31 @@ export default function VueMeta (Vue, options = {}) {
Vue.prototype.$meta = $meta(options)

// store an id to keep track of DOM updates
let requestId = null
let batchID = null

// watch for client side component updates
Vue.mixin({
beforeCreate () {
// coerce function-style metaInfo to a computed prop so we can observe
// it on creation
if (typeof this.$options[options.keyName] === 'function') {
this.$options.computed.$metaInfo = this.$options[options.keyName]
}
},
created () {
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
if (this.$metaInfo) {
this.$watch('$metaInfo', () => {
// batch potential DOM updates to prevent extraneous re-rendering
batchID = batchUpdate(batchID, () => this.$meta().refresh())
})
}
},
beforeMount () {
// batch potential DOM updates to prevent extraneous re-rendering
window.cancelAnimationFrame(requestId)

requestId = window.requestAnimationFrame(() => {
requestId = null
this.$meta().refresh()
})
batchID = batchUpdate(batchID, () => this.$meta().refresh())
}
})
}

0 comments on commit 471bfed

Please sign in to comment.