Ngrx server sided pagination with router parameters - javascript

I have a store with a simplified state tree:
{
routerReducer: {
state: {
url: '/blog'
},
queryParams: {
category: 'home'
}
params: { }
},
blog: {
posts: {
entities: { ... }
loading: false,
loaded: false,
pagination: {
sort: 'date',
filters: {
category: 'home',
tag: 'testTag'
},
page: 1
}
}
}
}
Basically, I'd like to pass down my router state into my blog state for pagination purposes, but only if the current URL belongs to that module if that makes sense? The pagination part of my blog -> posts state will be based on the URL parameters, already composed in my state. Perhaps this is not the correct method as I will no longer have a single source of truth? But I'd like to use this pagination state to essentially describe the set of entities I have in my store. That means, if I move pages or change filters, I plan on clearing all entities and refreshing with paginated content (performed server-side) from my API.
I supposed my flow will look like this:
Router navigation event e.g. /blog/2 (page 2 via queryParam)
Router action is dispatched and handled by router reducer to update
that part of my state tree
Side effect triggered on router navigation event, and if URL matches
my blog module e.g. "/blog/*" (could also contain URL parameters e.g.
?category=home) compose our local pagination state inside my blog state tree, then dispatch a loadPosts action which will be based off that piece of state
How does this flow sound? Is this the correct way of doing this?

1) It sounds feasable.
2) No. Whatever gets the job done.
What I would do
I'd create blog postPagination state where I would keep pagination data separate from entities. And a BlogPaginate action to alter it's state in reducer function.
{
{
sort: 'date',
filters: {
category: 'home',
tag: 'testTag'
}
},
page: 1
}
I'd make an effect that listens on router actions and maps the matching ones (url /blog/*) with appropriate search filters to BlogPaginate action which in turn would trigger a service call.
If you'd like to cache those entities
Making moving back to pages you've seen previously would be smoother than before. Depending on your content change rate I'd choose to either dispatch an action or just use the value in the cache if it exists.
Then I would add to postPagination state:
{
pageContents: {
// map of page to entity ids
1: [1,8,222]
}
{
sort: 'date',
filters: {
category: 'home',
tag: 'testTag'
}
},
currentPage: 1,
totalPages: 10
}
When pagination filters / sort changes in BlogPaginate reducer I would clear pageContents.
When pagination response's totalPages changes in BlogPaginateSuccess reducer I would clear other pageContents pages.
In BlogPaginateSuccess reducer I'd add/update new entities in blog posts and map their id's in as pageContents. Remember reducers can react to what ever action.
I would also create a selector that maps postPagination.currentPage, postPagination.pageContents and post.entities into an array of blog post entities.

Related

how not to lose state from parent component to child component

I have a view which shows all posts. There's a filter above those posts and user can filter posts depending on what options he chooses. After he chooses the options, filtered posts get returned.
Let's say user filtered posts and after that clicked on one of the posts, it means that parent component which was showing posts will be destroyed. If now, user(who is on the specific post page) clicks back button, it will take him to all posts page, but filters won't be persisted since parent component got destroyed and then created.
One solution to persist filters and filtered posts after clicking back button from specific page is to use vuex. when user chooses filters, we store the object in vuex. when user clicks back button, store would already have the filters. The problem is following this way causes some problems for me and takes much more time.
Any other way you can think of ? I can't use keep-alive since it seems i can only use it for dynamic components and not any other way.
I see 2 options here:
Vuex - it's used for state management, best to use when you need to communicate between 2+ components. You can will need a set of methods that will update the filter values in your store, e.g.:
const store = {
category: null,
tag: null,
date: null
}
const actions = {
updateFilter({ commit }, payload) {
commit('updateFilter', payload); // example payload structure: { filterName: 'category', filterValue: 'reviews' }
}
}
const mutations = {
updateFilter(state, payload) {
state[payload.filterName] = payload.filterValue;
}
}
export default {
namespaced: true,
store,
actions,
mutations
}
And you need to bind these actions to via #click events on your website. Then you need to bind the values from the store with your filters method (probably also you'll want to execute filtering method when your posts list changes, so you can use watcher for example)
If you're using Vue router and history mode, you can store your filters via query params:
router.push({ path: 'blog', query: { category: 'reviews' }})
So your url will become blog?category=reviews - and when you change your url to clicked article and then click back, you'll go first to the url with latest query params set you had (but of course you need to create a method that will filter out on component create the post list based on provided filters)
The additional win for the 2nd option is that you'll be able to share the link with other people (so they will gonna see the filtered posts in the same way as you do).

Avoiding Content re-fetching when State has already populated

I have the following Vue components structure:
Wrapper
-- QuestionsList
---- Question
-- QuestionDetail
And the following routes:
/ (goes to Questionslist)
/:id (goes to QuestionDetail)
When the user visits /, an HTTP GET request is done (via Vuex) and content is populated in an array of objects. Then, if he clicks on a "show more" link, he sees the QuestionDetail page (but without having to re-fetch content - it's taken directly from the State)
My problem is that every time we "route" in a page, we're not aware of the state of our Store.
For example, when the user navigates (deep-linked) to /:id, we need to see if the State is populated and therefore avoid making a new HTTP GET:
created() {
if (!this.$store.state.questions.length) {
this.$store.dispatch('requestItems');
}
},
computed: {
question() {
return this.$store.state.questions[this.$route.params.id] || {};
},
},
On a different scenario, since I always concat the new items fetched with the current state,I often see that data is duplicated:
[types.FETCHED_ADS_SUCCESS](state, payload) {
state.items = [...state.items, ...payload]; // when I go back in history, a new action is initiated and concats a copy of the same data
},
What is your store structure? I have found it useful to keep a boolean indicator in the store to indicate whether the content is loaded or not.
const store = new Vuex.store({
state: {
questions: {
isLoaded: false,
items: []
}
}
})
Then in the component where you're fetching the questions check for this and update once loaded.

Vue.js, pass data to component on another route

Simple task to pass data from one page to another made a real headache to newbie in Vue.js
Here is my router
I'm rendering a form on "/" page, which make an API request,
and I just want to pass data to another route page with another component
Is it so hard to do in Vue? It made a real headache for me today.
Get data from API, save & send to another component. How can I do it?
As Antony said, since you've already enabled props in router/index.js, you can pass parameters in router.push like this:
router.push({
name: 'events',
params: {
items: data
}
});
then you will be able to receive items as props in EventsDisplay.vue
export default {
name: 'eventsDisplay',
props: ['items'],
};
If you want to save it globally, you could use an event bus or a VueX store to contain it, then save and load it from this event bus or store when you need to access it.
If you want to pass it between those two components only, and only when accessing the second page from the first one, you can pass properties to the route argument as per this guide: https://router.vuejs.org/en/essentials/passing-props.html
Try using Vuex
In Source Component
this.$store.commit('changeDataState', this.$data);
this.$router.push({name: 'user-post-create'});
In Destination Component
created () {
console.log('state', this.$store);
this.$store.getters.Data;
}
Add props true in your vue router
{
path: "/pay-now",
name: "pay",
props: true,
component: () =>
import(/* webpackChunkName: "about" */ "../components/account/pay.vue"),
beforeEnter: AuthGuard,
},
After that pass props with router like that
this.$router.push({
name: "pay",
params: {
result:this.result,
}
});

Mithril component not updating when route changes

I am creating my personal website/blog as a a single page application using Mithril.js. All pages and blog posts on my website are rendered using Page and Post components, and the correct page is loaded based on the :slug in the URL.
The problem I have is that whenever I try and switch between pages, the content of the page does not update. Switching between pages and posts works because I am alternating between Page and Post components. But when I try and use the same component twice in a row, going from page to page, it doesn't update the webpage.
m.route(document.body, '/', {
// `Home` is a wrapper around `Page`
// so I can route to `/` instead of `/home`
'/': Home,
'/:slug': Page,
'/blog/:slug': Post
});
const Home = {
view() {
return m(Page, { slug: 'home' });
}
};
Here is the Page component (the Post component is very similar). Both components render correctly.
const Page = {
content: {},
oninit(vnode) {
m.request({
method: 'GET',
url: 'content.json',
}).then((response) => {
Page.content = response.pages[vnode.attrs.slug];
});
},
view() {
if (Page.content) {
return [
m('#content', m.trust(Page.content.body))
];
}
}
};
Why isn't Mithril recognizing that the slug changed?
The docs page for m.route has a solution for you.
When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from /page/1 to /page/2 given a route /page/:id, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the onupdate hook, rather than oninit/oncreate. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event.

How to sync Redux state and url hash tag params

We have a list of lectures and chapters where the user can select and deselect them. The two lists are stored in a redux store.
Now we want to keep a representation of selected lecture slugs and chapter slugs in the hash tag of the url and any changes to the url should change the store too (two-way-syncing).
What would be the best solution using react-router or even react-router-redux?
We couldn't really find some good examples where the react router is only used to maintain the hash tag of an url and also only updates one component.
I think you don’t need to.
(Sorry for a dismissive answer but it’s the best solution in my experience.)
Store is the source of truth for your data. This is fine.
If you use React Router, let it be the source of truth for your URL state.
You don’t have to keep everything in the store.
For example, considering your use case:
Because the url parameters only contain the slugs of the lectures and the chapters which are selected. In the store I have a list of lectures and chapters with a name, slug and a selected Boolean value.
The problem is you’re duplicating the data. The data in the store (chapter.selected) is duplicated in the React Router state. One solution would be syncing them, but this quickly gets complex. Why not just let React Router be the source of truth for selected chapters?
Your store state would then look like (simplified):
{
// Might be paginated, kept inside a "book", etc:
visibleChapterSlugs: ['intro', 'wow', 'ending'],
// A simple ID dictionary:
chaptersBySlug: {
'intro': {
slug: 'intro',
title: 'Introduction'
},
'wow': {
slug: 'wow',
title: 'All the things'
},
'ending': {
slug: 'ending',
title: 'The End!'
}
}
}
That’s it! Don’t store selected there. Instead let React Router handle it. In your route handler, write something like
function ChapterList({ chapters }) {
return (
<div>
{chapters.map(chapter => <Chapter chapter={chapter} key={chapter.slug} />)}
</div>
)
}
const mapStateToProps = (state, ownProps) => {
// Use props injected by React Router:
const selectedSlugs = ownProps.params.selectedSlugs.split(';')
// Use both state and this information to generate final props:
const chapters = state.visibleChapterSlugs.map(slug => {
return Object.assign({
isSelected: selectedSlugs.indexOf(slug) > -1,
}, state.chaptersBySlug[slug])
})
return { chapters }
}
export default connect(mapStateToProps)(ChapterList)
react-router-redux can help you inject the url stuff to store, so every time hash tag changed, store also.

Categories