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).
Related
In my Vue app, I have a set of filters that build a query string upon selection and append it to the url. I want to have the filters applied and the data to be refreshed while also preserving state so that the user can keep adding filters without the modal being closed. I thought the only property would achieve this but it does not, the page still loses state. Here is what I've tested so far:
This achieves what I want (modal is not closed) but the data is not updated.
this.$inertia.visit(url, { preserveState: true })
This updates the data correctly, but still loses state as if the preserveState property is overidden (modal is closed)
...
props: ['data'],
...
this.$inertia.visit(url, {
preserverState: true,
only: ['data']
})
Is this possible with another method of hitting the url or custom option?
After some messing with configuration options, I found a way to update data while preserving state. Simply use the onSuccess option while preserving state:
...
props: ['data'],
...
this.$inertia.visit(url, {
preserveState: true,
onSuccess: page => {
this.data = page.props.data;
}
})
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.
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.
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.
In Aurelia, I have a parent component that is composed of several other components. To keep this example simple, say that one component is a State dropdown, and another component is a City dropdown. There will be other components that depend on the selected city, but those two are enough to illustrate the issue. The parent view looks like this:
<template>
<compose view-model="StatePicker"></compose>
<compose view-model="CityPicker"></compose>
</template>
The idea is that you would pick a state from the StatePicker, which would then populate the CityPicker, and you would then pick a city which would populate other components. The routes would look like /:state/:city, with both being optional. If :state is omitted, then the url will automatically redirect to the first available state.
I'm using the EventAggregator to send messages between the components (the parent sends a message to the CityPicker when a state is selected). This is working, except for the initial load of the application. The parent is sending the message to the CityPicker, but the CityPicker component hasn't been activated yet, and it never receives the message.
Here's a Plunker that shows the problem. You can see that the city dropdown is initally empty, but it starts working once you change the state dropdown. Watch the console for logging messages.
So the question is: Is there a way to know that all the components are loaded before I start sending messages around? Is there a better technique that I should be using? Right now, the StatePicker sends a message to the parent that the state has changed, and then the parent sends a message to the CityPicker that the state has changed. That seems a little roundabout, but it's possible that the user could enter an invalid state in the url, and I liked the idea of being able to validate the state in one place (the parent) before all the various other components try to load data based on it.
The view/viewModel Pattern
You would want your custom elements to drive data in your viewModel (or in Angular / MVC language, controller). The viewModel captures information about the current state of the page. So for example, you could have a addressViewModel route that has state and city properties. Then, you could hook up your custom elements to drive data into those variables. Likewise, they could listen to information on those variables.
Here's an example of something you might write:
address.html
<state-picker stateList.one-way="stateList" value.bind="state" change.delegate="updateCities()"></state-picker>
<city-picker cityList.one-way="cityList" value.bind="city"></city-picker>
address.js
class AddressViewModel {
state = null;
city = null;
stateList = ['Alabama', 'Alaska', 'Some others', 'Wyoming'];
cityList = [];
updateCities() {
let state = this.state;
http.get(`API/cities?state=${state}`) // get http module through dependency injection
.then((response) => {
var cities = response.content;
this.cities.length = 0; // remove current entries
Array.prototype.push.apply(this.cities, cities);
});
}
}
If you wanted to get a little more advanced and isolate all of your state and city logic into their respective custom elements, you might try following this design pattern:
address.html
<state-picker value.bind="state" country="US"></state-picker>
<city-picker value.bind="city" state.bind="state"></city-picker>
address.js
class cityPickerViewModel {
#bindable
state = null;
cities = [];
constructor() {
// set up subscription that listens for state changes and calls the update
// cities function, see the aurelia documentation on the BindingEngine or this
// StackOverflow question:
// http://stackoverflow.com/questions/28419242/property-change-subscription-with-aurelia
}
updateCities() {
/// same as before
}
}
The EventAggregator Pattern
In this case, you would not want to use the EventAggregator. The EventAggregator is best used for collecting various messages from disparate places in one central location. For example, if you had a module that collected app notifications in one notification panel. In this case, the notification panel has no idea who might be sending messages to it, so it would just collect all messages of a particular type; likewise, any component could send messages whether or not there is a notification panel enabled.