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;
}
})
Related
I'm using a vaadin-dialog in my Web Components app. I would like the components inside to render on properties change but they don't.
I have a couple properties in my web component:
static get properties() {
return {
title: { type: String },
username: { type: String },
signUpOpened: { type: Boolean },
};
}
The dialog is defined as such. I use a function to bind it to the DialogRenderer. The dialog shouldn't be always open, so the user clicks it to open it.
render() {
return html`
<main>
<h1>${this.title}</h1>
<vaadin-dialog
header-title="SignUp"
.opened="${this.signUpOpened}"
#opened-changed="${e => (this.signUpOpened = e.detail.value)}"
${dialogRenderer(this.signUpRenderer)}
id="signUp">
</vaadin-dialog>
</main>
`;
}
signUpRenderer() {
return html`
<vaadin-text-field value="${this.username}" #change="${(e) => {
this.username = e.target.value
}}" label="email"></vaadin-text-field>
<vaadin-text-field value="${this.username}" }}" label="email-copy"></vaadin-text-field>
<p>${this.username}</p>
`;
}
You can try the demo over here, the source code is here.
When changing the value of the text field, you can see that the other text field and the paragraph don't update.
Can you folks direct me towards what is going on?
The dialogRenderer directive that you are using to render the contents of the dialog needs to know whether the data used in the rendering has changed in order to determine whether it should rerender the contents or not.
For that purpose the directive has a second parameter that allows you to pass in an array of dependencies that will be checked for changes.
In your example, you should apply the directive like so, in order for it to re-render when the username changes:
<vaadin-dialog
...
${dialogRenderer(this.signUpRenderer, [this.username])}>
</vaadin-dialog>
This is explained in the directive's JSdoc, unfortunately the directives do not show up in the component's API docs at the moment. You could open an issue here and request to improve the component documentation to add a section that explains how the directives work.
I have started learning state management using NGXS. So far everything is fine but have few questions regarding some scenarios like -
If a Mat Dialog box is open (or any div - here I've both the scenarios in my project) and from inside it an API is called, how can I close the dialog only if API returns success?
Suppose a user logs out, how can I reset the states to default values?
For the first case below is my code for the state, action & dispatcher:
abc.action.ts
export class AddExamCategory {
static readonly type = '[ExamCategory] Add';
constructor(public payload: ExamCategory) {}
}
abc.state.ts
export interface ExamCategoryStateModel {
examCategoryList: ExamCategory[];
}
#State<ExamCategoryStateModel>({
name: 'examCategory',
defaults: {
examCategoryList: []
}
})
#Injectable()
export class ExamCategoryState {
constructor(private _adminService: AdminService) {}
#Action(AddExamCategory)
addExamCategory({ getState, patchState }: StateContext<ExamCategoryStateModel>, { payload }: AddExamCategory) {
return this._adminService.createExamCategory(payload).pipe(tap(response => {
patchState({ examCategoryList: [] }); // Want to close the modal/div after this part. If API returns ERROR do not close.
}));
}
}
abc.component.ts
this.store.dispatch(new AddAdminUser({ ...this.adminRegistrationForm.value, password: this.password.value }))
.subscribe(response => {
this.store.dispatch(new GetAdminUsers());
this.dialogRef.close(true)
});
Currently it's like this but it closes no matter what's the status of API.
For the second case, in the service where I have written the logic for logout() I have written like this: this.store.reset({}). Though it's resetting the state but not with the default values in it. I have multiple states to reset on this single logout method.
How to work on these scenarios?
You can add extra property on your state to track the requesting state of your application ('requesting','idle') [you can create extra states as needed to track 'success' and 'error' response from the server]
when dispatch GetAdminUsers set the value of the newely added state to requesting and at GetAdminUsersComplete set the value to idle
subscribe to a selector that's read the state on your ngOnInit and call dialogRef.clse(true) inside of it. like following:
this.store
.pipe(
select(selectors.selectRequestState),
skip(1) //only start tracking after request created
)
.subscribe(result => {
if (result == 'idle')
this.dialogRef.close()
});
example: https://stackblitz.com/edit/angular-ivy-4ofc3q?file=src/app/app.component.html
Reset State
I don't think there is a simple way to reset the state with the store. You need to move through all your state features and implement reset action that set the state to initial state.
the simplest solution is to refresh the browser page after user logout using location.reload();
if you keep the store inside localstorage you need to remove it first then do the reload
the easiest way to accomplish that is with an effect, after you dispatch your action you should be able to trigger other action like "UsserAddedSuccessfully" and read it after that close that modal.
read this question for more detail.
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).
In order to properly copy props to local data and manipulate them in your component you need to use Computed props.
What do you do when you want to set the default value of a computed prop to be based on a prop but to also be able to override its' value manually without resetting the entire computed property?
props: {
thing: {
type: Object,
required: false,
default: null,
},
},
computed: {
form() {
return {
name: this.thing.name,
someLocalThing: 'George Harrington',
};
},
}
and then
<v-text-field v-model="form.name">
<v-text-field v-model="someLocalThing">
The problem is, changing someLocalThing, overrides/resets form.name (the computed prop is re-evaluated) so we lose the changes we had just previously done.
edit: this is an exact reproduction link : https://codesandbox.io/s/vuetify-2x-scope-issues-ct0hu
You could do something like this
props: {
thing: {
type: Object,
required: false,
default: null,
},
},
data () {
return {
name: this.thing.name,
someLocalThing: 'George Harrington'
}
}
Now you can modify the data inside of component, and the props are still the same.
If you want to apply these changes directly to parent component as well, you will have to emit a function with the updated data.
I had the same issue today. In my use case, users are opening a dialog to edit an object, My solution was to fill the form on the button press to open the dialog. This seems to be a Vuetify issue. I have searched the Vuetify Github repo, but I have not been able to find this issue.
Regardless, here is my implementation (trimmed down for brevity).
#click="fillForm()" calls a function to fill the v-textarea s
<v-dialog v-model="dialog" persistent max-width="600px">
<template v-slot:activator="{ on }">
<v-btn color="primary" v-on="on" #click="fillForm()" width="100%">Edit RFQ</v-btn>
</template>
...
</v-dialog>
script
export default {
props: ['rfq'],
data(){
return {
title: undefined,
notes: undefined,
companiesRequested: undefined,
}
},
methods: {
fillForm() {
this.title = this.title ? this.title : this.rfq.title;
this.notes = this.notes ? this.notes : this.rfq.notes;
this.companiesRequested = this.companiesRequested ? this.companiesRequested : this.rfq.company_requested_ids;
},
}
}
In order to properly copy props to local data and manipulate them
First, in your example you have no local data. What your computed property is is some temporary object, which can be recreated any time something changes. Don't do that.
If the prop is really just some initial value, you can introduce property in data() and initialize it right there from prop
If you are passing the prop to component in order to change its value, you probably want that changed value passed back to your parent. In that case you don't need to copy anything. Just props down, events up (either with v-model, .sync or just handling events manually)
In the case Object is passed by prop, you can also directly mutate object's properties - as long as you don't change prop itself (swap it for different object), everything is fine - Vue will not throw any warning and your parent state will be mutated directly
UPDATE
Now I have better understanding of use case. Question should be more like "Best way to pass data into a modal popup dialog for editing item in the list/table"
Dialog is modal
Dialog can be shown for specific item in a table by clicking button in it's row
Dialog is cancelable (unless the user uses Save button, any changes made in the dialog should be discarded)
In this particular case I don't recommend using props to pass the data. Props are good for passing reactive values "down". Here you don't need or want dialog to react for data changes. And you also don't want use props for 1-time initialization. What you want is to copy data repeatedly at particular time (opening the dialog).
In component with table (row rendering):
<td>
<v-btn rounded #click="openDialog(item)">open details</v-btn>
</td>
Place dialog component outside the table:
<person-form #update="onUpdate" ref="dialog"></person-form>
In methods:
openDialog(item) {
this.$refs.dialog.openDialog(item);
},
onUpdate(item) {
// replace old item in your data with new item
}
In dialog component:
openDialog(item) {
this.form = { ...item }; // copy into data for editing
// show dialog....
},
// Save button handler
onSave() {
this.$emit("update", this.form);
// hide dialog...
}
Demo
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.