Search code examples
wordpressrestvue.jsvuejs2wordpress-rest-api

fetch post by categories via rest api - wordpress and vuejs


I'm trying to fetch some post data through the Wordpress Restful API. What I achivied until now is:

  1. when the app is loaded, fetch the first page of posts.
  2. then, if the user click the button 'load more posts', another page of posts is fetched, until there is no more posts to display

first of all i create this function in the main.js file:

Array.prototype.next = function*(){
  for(let item of this){
    yield item
  }
}

then in the App.vue, I have this:

created(){
    this.$store.dispatch('setCategories') //grab posts categories
      .then(resolve=> this.$store.dispatch('setPostsCount')) //grab the number of post pages and the total number of posts
        .then(resolve=>this.$store.dispatch('loadPosts')) load the first page of posts
          .catch(err => console.log(err))
  }

this is the store module post.js:

  import axios from 'axios'

const postsRequest = axios.create({
    baseURL: 'https://wordpress-site/wp-json/wp/v2/posts'
  })






const state = {
    posts:[],
    filteredPosts:[],
    totalPages:null,
    totalPosts:null
}


const getters = {
    getPosts(state){
        return  state.posts
    },
    getFilteredPosts(state){
        return state.filteredPosts
    },
    getAllPostsPages(state){
        return state.totalPages
    },
    getAllPostsNumber(state){
        return state.totalPosts
    },
    getNextPage(state,getters){
        return getters.getAllPostsPages.next()
    }
}


const mutations = {
    'SET_POSTS_COUNT'(state,headers){
        state.totalPages = [...Array(parseInt(headers['x-wp-totalpages'])).keys()].map(page => page+1),
        state.totalPosts = parseInt(headers['x-wp-total']) //[...Array(parseInt(headers['x-wp-total'])).keys()]
    },
    'LOAD_POSTS'(state,posts){
        for(let post of posts){
            state.posts.push(post)
        }
        console.log(state.posts.length)   
    },
    'FILTER_BY_CATEGORY'(state,posts){
        state.filteredPosts = []
        state.filteredPosts = posts
    },
    'EMPTY_FILTERED_POSTS'(state){
        state.filteredPosts = []
    }
}


const actions = {
    setPostsCount({commit}){
        return new Promise((resolve,reject)=>{
            postsRequest.get().then(response => {
                commit('SET_POSTS_COUNT',response.headers)
                resolve()
            })  
        })

    },
    loadPosts({commit,getters}){
        let nextPage = getters.getNextPage.next()
        if(!nextPage.done){
            postsRequest.get(this.baseURL,{
                params:{
                    page: nextPage.value
                }
            }).then(response=> {

                commit('LOAD_POSTS',response.data)
            })
            .catch(err => console.log(err))
        }
    },
    loadPostsByCategory({commit,getters},index){
            postsRequest.get(this.baseURL,{
                params:{
                    categories:index
                }
            }).then(response => commit('FILTER_BY_CATEGORY',response.data))
                .catch(err => console.log(err))

    },
    loadPostsByCat({commit,getters},category){
        ...
    }
}

export default {
    state,
    getters,
    actions,
    mutations
  }

and this is the component where i display the posts:

 <template>
    <div class="gy-read">
        <div @click="emptyFilteredPosts()">all</div>
        <div style="display:inline-block;margin-left:20px" v-for="category in categories" :key="category.id">
            <h3 @click="searchByCategory(category.index)">{{category.name}}</h3>
        </div>
        <div v-for="post in posts" :key="post.id">
            <h2>{{post.title.rendered}}</h2>
            <h4>{{post.date}}</h4>
            <h4>{{post.categories}}</h4>
        </div>
        <div>
            <button v-if="!currentCategory" @click="loadMorePosts()">load more</button>
            <button v-else @click="loadMorePostsByCat()">load more cat</button>
        </div>


    </div>

</template>

<script>

export default {
    data(){
        return{
            currentCategory:null,
        }
    },
 computed:{
     posts(){
         return this.$store.getters.getFilteredPosts.length ? this.$store.getters.getFilteredPosts : this.$store.getters.getPosts
     },
     categories(){
         return this.$store.getters.getCategories //this simply create an array of category index
     },
 },
 methods:{
     loadMorePosts(){
        this.$store.dispatch('loadPosts')

     },
     loadMorePostsByCat(){
         this.$store.dispatch('loadPostsByCat',this.currentCategory)
     },
     searchByCategory(index){
        this.currentCategory = index;
        this.$store.dispatch('loadPostsByCategory',index)
     },
     emptyFilteredPosts(){
         this.$store.commit('EMPTY_FILTERED_POSTS');
         this.currentCategory = null;
     }
 }

}
</script>

<style lang="scss">
    .gy-read{
        margin-top:150px;
    }
</style>

Now I'm stuck: once the user click on a category, the state.posts list is replaced with the state.filteredPosts list, that contains the first 10 posts fetched by category via Rest api (see the searchByCategory method).

Now, I want that the load more button upgrade the list of posts only with those that have the same category, in addition to those already present.

Is this possible, or I have to reconsider my implementation?

This implementation works fine only with the total amount of posts, regardless of the categories.

I cannot modify the php, I have to work only with vue.

Thanks!


Solution

  • Ok I solved in this way. As suggested by Sphinx, when category changes I reset the array of post to 0, and make another request, using also an offset base of the number of posts already present. Seems that this works:

    component:

    <template>
        <div class="gy-read">
            <div class="category">
                <div @click="currentCategory='all'">all</div>
                <div v-for="category in categories" :key="category.id">
                    <h3 @click="currentCategory = category.index">{{category.name}}</h3>
                </div>
            </div>
            <div class="card__container">
                <router-link tag='div' class="card" :to="{name:'article', params: {slug: post.slug, post:post}}" v-for="post in posts" :key="post.id">
                <div :style="{ backgroundImage: 'url(' + post.better_featured_image.source_url+ ')' }" class="card__img"></div>
                <div class="card__content">
                    <h2>{{post.title.rendered}}</h2>
                    <span v-for="cat in post.categories" :key="cat">{{ cat}}</span>
                </div>  
            </router-link>
            </div>
            <div class="load">
                <button ref="button" v-if="posts.length" @click="loadMorePosts()">load more</button>
            </div>
    
    
        </div>
    
    </template>
    
    <script>
    export default {
        data(){
            return{
                currentCategory:null,
            }
        },
        created(){
    
        },
     computed:{
         posts(){
             return this.$store.getters.getPosts
         },
         categories(){
             return this.$store.getters.getCategories
         }
    
     },
     watch:{
         currentCategory: function(cat){
             console.log(cat)
             this.$store.commit('RESET_POSTS')
             this.loadMorePosts()
         }
    
     },
     methods:{
         loadMorePosts(){
            this.$store.dispatch('loadPosts',this.currentCategory)
         },
     }
    
    }
    </script>
    

    store:

    import axios from 'axios'
    
    
    const state = {
        posts:[]
    }
    
    
    const getters = {
        getPosts(state){
            return  state.posts
        },
    }
    
    const mutations = {
        'LOAD_POSTS'(state,posts){
            for(let post of posts){
                state.posts.push(post)
            }  
        },
        'RESET_POSTS'(state){
            state.posts = []
        },
    }
    
    
    const actions = {
        loadPosts({commit,getters},category){
                axios.get('/posts',{
                    params:{
                        categories: category === 'all' ? null : category,
                        offset: getters.getPosts.length
                    }
                }).then(response=> {
                    if(response.data.length){
                        console.log(response.data)
                        commit('LOAD_POSTS',response.data)
                    }else{
                        //this._vm.$emit('noMorePosts',null)
    
                    }
    
                })
                .catch(err => console.log(err))
    
    
    
        }
    }
    
    export default {
        state,
        getters,
        actions,
        mutations
      }
    

    App.vue created hook:

    created(){
        this.$store.dispatch('setCategories')
            .then(resolve=>this.$store.dispatch('loadPosts','all'))
              .catch(err => console.log(err))
      }