Commit 01d04adc authored by Evan You's avatar Evan You

handle view param changes and user not found

parent 8255a01b
...@@ -2,15 +2,17 @@ import Vue from 'vue' ...@@ -2,15 +2,17 @@ import Vue from 'vue'
import 'es6-promise/auto' import 'es6-promise/auto'
import { createApp } from './app' import { createApp } from './app'
// a global mixin to invoke fetchData on the client // a global mixin that calls `asyncData` when a route component's params change
Vue.mixin({ Vue.mixin({
beforeMount () { beforeRouteUpdate (to, from, next) {
const { asyncData } = this.$options const { asyncData } = this.$options
if (asyncData) { if (asyncData) {
this.dataPromise = asyncData( asyncData({
this.$store, store: this.$store,
this.$route route: to
) }).then(next)
} else {
next()
} }
} }
}) })
...@@ -26,6 +28,17 @@ if (window.__INITIAL_STATE__) { ...@@ -26,6 +28,17 @@ if (window.__INITIAL_STATE__) {
// wait until router has resolved all async before hooks // wait until router has resolved all async before hooks
// and async components... // and async components...
router.onReady(() => { router.onReady(() => {
// add router hook for handling asyncData
// doing it after initial route is resolved so that we don't double-fetch
// the data that we already have.
router.beforeResolve((to, from, next) => {
Promise.all(router.getMatchedComponents(to).map(c => {
if (c.asyncData) {
return c.asyncData({ store, route: to })
}
})).then(next)
})
// actually mount to DOM // actually mount to DOM
app.$mount('#app') app.$mount('#app')
}) })
......
...@@ -27,11 +27,11 @@ export default context => { ...@@ -27,11 +27,11 @@ export default context => {
// which is resolved when the action is complete and store state has been // which is resolved when the action is complete and store state has been
// updated. // updated.
Promise.all(matchedComponents.map(component => { Promise.all(matchedComponents.map(component => {
return component.asyncData && component.asyncData( return component.asyncData && component.asyncData({
store, store,
router.currentRoute, route: router.currentRoute,
context ssrContext: context
) })
})).then(() => { })).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`) isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
// After all preFetch hooks are resolved, our store is now // After all preFetch hooks are resolved, our store is now
......
...@@ -44,6 +44,6 @@ export default { ...@@ -44,6 +44,6 @@ export default {
FETCH_USER: ({ commit, state }, { id }) => { FETCH_USER: ({ commit, state }, { id }) => {
return state.users[id] return state.users[id]
? Promise.resolve(state.users[id]) ? Promise.resolve(state.users[id])
: fetchUser(id).then(user => commit('SET_USER', { user })) : fetchUser(id).then(user => commit('SET_USER', { id, user }))
} }
} }
...@@ -17,7 +17,7 @@ export default { ...@@ -17,7 +17,7 @@ export default {
}) })
}, },
SET_USER: (state, { user }) => { SET_USER: (state, { id, user }) => {
Vue.set(state.users, user.id, user) Vue.set(state.users, id, user || false) /* false means user not found */
} }
} }
...@@ -10,9 +10,9 @@ export default function createListView (type) { ...@@ -10,9 +10,9 @@ export default function createListView (type) {
return { return {
name: `${type}-stories-view`, name: `${type}-stories-view`,
asyncData (store, route, context) { asyncData ({ store, ssrContext }) {
return store.dispatch('FETCH_LIST_DATA', { type }).then(() => { return store.dispatch('FETCH_LIST_DATA', { type }).then(() => {
setTitle(camelize(type), context) setTitle(camelize(type), ssrContext)
}) })
}, },
......
...@@ -46,20 +46,33 @@ export default { ...@@ -46,20 +46,33 @@ export default {
} }
}, },
asyncData (store, { params: { id }}, context) { // We only fetch the item itself before entering the view, because
// it might take a long time to load threads with hundreds of comments
// due to how the HN Firebase API works.
asyncData ({ store, route: { params: { id }}, ssrContext }) {
return store.dispatch('FETCH_ITEMS', { ids: [id] }).then(() => { return store.dispatch('FETCH_ITEMS', { ids: [id] }).then(() => {
const item = store.state.items[id] const item = store.state.items[id]
setTitle(item.title, context) setTitle(item.title, ssrContext)
}) })
}, },
// on the client, fetch all comments // Fetch comments when mounted on the client
beforeMount () { beforeMount () {
this.dataPromise.then(() => { this.fetchComments()
},
// refetch comments if item changed
watch: {
item: 'fetchComments'
},
methods: {
fetchComments () {
this.loading = true
fetchComments(this.$store, this.item).then(() => { fetchComments(this.$store, this.item).then(() => {
this.loading = false this.loading = false
}) })
}) }
} }
} }
......
<template> <template>
<div class="user-view"> <div class="user-view">
<spinner :show="!user"></spinner> <spinner :show="!userLoaded"></spinner>
<template v-if="user"> <template v-if="user">
<h1>User : {{ user.id }}</h1> <h1>User : {{ user.id }}</h1>
<ul class="meta"> <ul class="meta">
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
<a :href="'https://news.ycombinator.com/threads?id=' + user.id">comments</a> <a :href="'https://news.ycombinator.com/threads?id=' + user.id">comments</a>
</p> </p>
</template> </template>
<template v-else-if="user === false">
<h1>User not found.</h1>
</template>
</div> </div>
</template> </template>
...@@ -27,13 +30,16 @@ export default { ...@@ -27,13 +30,16 @@ export default {
computed: { computed: {
user () { user () {
return this.$store.state.users[this.$route.params.id] return this.$store.state.users[this.$route.params.id]
},
userLoaded () {
return this.$route.params.id in this.$store.state.users
} }
}, },
asyncData (store, { params: { id }}, context) { asyncData ({ store, route: { params: { id }}, ssrContext }) {
return store.dispatch('FETCH_USER', { id }).then(() => { return store.dispatch('FETCH_USER', { id }).then(() => {
const user = store.state.users[id] const user = store.state.users[id]
setTitle(user.id, context) setTitle(user ? user.id : 'User not found', ssrContext)
}) })
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment