How to find/replace specific object in Vuex? - vue.js

I have simple image upload via action and mutation. The main thing is that I firstly want to store preloader and then, when the image is uploaded to the server show the image and remove preloader.
export default new Vuex.Store({
state: {
images: [],
},
mutations: {
addImage(state, image) {
state.images.push(image);
}
},
actions: {
saveImage({commit}, image) {
commit('addImage', {url: 'http://via.placeholder.com/100x100?text=loading...'});
let form_data = new FormData();
form_data.append('file', image);
return new Promise((resolve, reject) => {
jQuery.ajax({
url : '/api/v1/products/files/save',
data : form_data,
type : 'POST',
cache: false,
contentType: false,
processData: false,
success: response => {
// commit('addImage', response.file);
// here I somehow need to replace/readd the response.file
resolve(response.file)
}
}).fail(() => reject())
})
}
},
strict: debug
})
I basically could use array.find method to look for url:http://via.placeholder.com/100x100?text=loading... remove it and then add new one but not sure if it's is the right method...

You give vuex action too much to do. Let it do one single task - upload the file. The placeholder/preloader should be handled by the component even without a vuex state. All the component needs to know is whether the image is uploading or not in order to show the loading indicator. Since your action returns a Promise your component will know when it's ready.

Related

How do I re-render a table component on screen after changing json data in Vue?

I am at a beginner level and learning vue at the moment.
All I am trying to do at the moment is having the table reload(re-render) itself so that the change from db.json file is applied on the table on the screen. I created a modal that adds data (name, email, contacts) to the db.json file once the save button is clicked.
However, the problem is that I have to manually reload the page (by pressing ctrl+R) in order for the changed data to be applied on the table.
Here is the script section of "Modal.vue" file (child component)
<script>
export default {
name: "TeamAddModal",
props: {
visible: Boolean,
variant: String,
},
data() {
return {
openClose: this.visible,
memberName: "",
memberEmail: "",
memberContacts: "",
};
},
methods: {
showModal() {
this.openClose = !this.openClose;
},
handleSave() {
if (this.memberName.length > 0) {
console.log(this.memberName, this.memberEmail, this.memberContacts);
this.openClose = !this.openClose;
let userData = {
name: this.memberName,
email: this.memberEmail,
contacts: this.memberContacts,
};
fetch("http://localhost:3000/teaminfo", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData),
})
.then(() => {
this.$router.push("/");
})
.catch((err) => console.log(err));
this.memberName = "";
this.memberEmail = "";
this.memberContacts = "";
}
},
},
watch: {
visible: function (newVal, oldVal) {
this.openClose = newVal;
console.log("new" + newVal + "==" + oldVal);
},
},
};
</script>
I would like the parent component(with the table) to re-render and show the change in json data once the 'save' button is clicked from the modal.
I have tried searching up for the solution in google and youtube, and it seems that :key may do the work, but I'm not sure if that really is the solution. I'll be very grateful for the help.
Once you get success response from the POST method API call on save, There could be a two solutions :
You can emit the userData object from modal component to parent and push this emitted object in the table data array.
On successful save, You can emit a success flag from modal component to parent component and then invoke a GET call to receive real time data from an API itself and bind the whole data into table.
In modal component :
handleSave() {
...
...
let userData = {
name: this.memberName,
email: this.memberEmail,
contacts: this.memberContacts,
};
fetch("http://localhost:3000/teaminfo", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData),
})
.then(() => {
this.$emit('save-success'); ✅
this.$router.push("/"); ❌
})
}
In parent component :
<modal-component #save-success="getTableData"></modal-component>
getTableData() {
// Make an API call to get the real time updated data and assign that to the table data items variable.
}

Vue.js render new data from API call

I am making a weather app written in Vue.js, which fetches weather data periodically, but I have an issue rendering new data after the initial API call.
The empty data array is declared in the data, and a timer is used to fetch new data, as such:
data() {
return {
weatherData: [],
timer: '',
};
},
I have declared the data fetching in methods, as such:
methods: {
async fetchWeatherData() {
const response = await fetch("http://localhost:5002/timeseries");
const data = await response.json();
if (response.ok) {
console.log("Data fetched sucssefully!");
}
return data;
},
And when the app loads, the fetchWeatherData and setInterval is initiated:
async created() {
this.weatherData = await this.fetchWeatherData();
setInterval(() => this.timer = this.fetchWeatherData(), 10000)
},
The problem is that the new data is not rendered to the DOM, although new data is fetched successfully.
What would be the best step to ensure that the new data is rendered correctly upon successfull fetch?
-HK
In the component (or any container) where you render the weather data, add a key (like :key="renderWeatherKey"). Add renderWeatherKey to component data.
data() {
return {
weatherData: [],
timer: '',
renderWeatherKey: 0
};
},
In the method fetchWeatherData(), inside 'if' condition, add this.renderWeatherKey++ :
async fetchWeatherData() {
const response = await fetch("http://localhost:5002/timeseries");
const data = await response.json();
if (response.ok) {
console.log("Data fetched sucssefully!");
this.renderWeatherKey++
}
return data;
},
You can force the re rendered with that.
The solution, as posted by James Thomson over at the Vue forum, was to set the setInterval to async, since the fetch method also is async. See the full answer here.

vueJS3 - How to trigger a function on event from sibling component

I want to trigger a function that GETs data from a http-server in a component, as soon as a button in a sibling component was pressed.
SignUpForm.vue has a button that triggers customSubmit()
customSubmit(){
//POST to API
const user = {
method: "POST",
headers: { "Content-Type": "application/json"},
body: JSON.stringify({newUser: this.newUser})
};
fetch("http://localhost:3080/api/user", user)
.then(response => response.json())
.then(data => console.log(data));
this.$emit('refresh', true)
this.clearForm();
}
The parent component looks as follows:
<template>
<div>
<SignUpForm #refresh="triggerRefresh($event)" />
<!-- <Exp /> -->
<Datatable :myRefresh="myRefresh" />
</div>
</template>
<script>
import SignUpForm from "./components/SignUpForm.vue";
import Datatable from "./components/Datatable.vue";
import Exp from "./components/exp copy.vue";
export default {
name: "App",
components: { Datatable, SignUpForm, Exp },
data() {
return {
myRefresh: false,
};
},
methods: {
triggerRefresh(bool) {
this.myRefresh = bool;
console.log(this.myRefresh);
},
},
};
</script>
Now i want the sibling component Datatable.vue
to fetch data from the server as soon, as this.$emit('refresh', true) is fired in SignUpForm.vue
Here's the script from Datatable.vue
export default {
data() {
return {
//Liste aller User
userData: null,
//temporärer User für das Details-Feld
printUser: [{ name: "", email: "", number: "" }],
//Property für den "read-Button"
showDetails: false,
//Property für den "Update-Button"
readOnly: true,
};
},
props: ["myRefresh"],
methods: {
pushFunction() {
fetch("http://localhost:3080/api/users")
.then((res) => res.json())
.then((data) => (this.userData = data));
},
readData(k) {
this.printUser.length = 0;
this.showDetails = true;
this.printUser.push(this.userData[k]);
},
editData(rowUser) {
if (!rowUser.readOnly) {
rowUser.readOnly = true;
const user = {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userData: this.userData }),
};
fetch("http://localhost:3080/api/users/patch", user)
.then((response) => response.json())
.then((data) => console.log(data));
} else {
rowUser.readOnly = false;
}
},
deleteData(k) {
fetch("http://localhost:3080/api/users/" + k, { method: "DELETE" }).catch(
(err) => console.log(err)
);
this.pushFunction();
},
//blaue Reihen
toggleHighlight(rowUser) {
if (rowUser.readOnly === false) {
return;
}
rowUser.isHighlight = !rowUser.isHighlight;
},
scrollDown() {
window.scrollTo(0, document.body.scrollHeight);
},
},
mounted() {
fetch("http://localhost:3080/api/users")
.then((res) => res.json())
.then((data) => (this.userData = data));
},
};
I really hope somebody can help a newbie out!
Two considerations, is it possible? and is it prudent?
Is it possible?
Yes, it is and you can implement it couple different ways.
Is it prudent?
No.
If you're going down this road, most likely the architecture is ineffective. In an ideal setup, your components should be responsible for managing the view only. That means what the user sees and collecting their input. The business logic should not live in the components. So if you have things like ajax calls and you put them into your component, you've coupled the logic to the view. One possible issue is that if the component is re-added for some reason, any in-progress ajax calls could be disrupted in an unexpected manner. While such scenarios can be handled, the bigger issue IMHO is that that when you are coupling business logic with the view layer you are creating an application that becomes increasingly difficult to reason about; This problem you have with sending event between sibling components is just one example.
Other options
The most common way, though not the only way, of dealing with this is by using a global store via Vuex.
Instead of initializing the Ajax request from your component, you call the Vuex action.
The action would usually set loading state either using single state variable (ie loadState=STATE.STARTED) or using isLoading=true, except instead of assigning the variable, vuex would do it through a mutation, so store.commit('setLoadState', STATE.LOADING). this will update the state in all components that are listening for changes in either the store directly or using a getter. Then the ajax request is made, and when it is done the store is updated again, either with store.commit('setLoadState', STATE.ERROR) or on success, store.commit('setLoadState', STATE.DONE) and store.commit('setUsers', response). Then your components only need to listen for changes, you can display a spinner if $store.loadState == STATE.LOADING
As long as the data for the subsequent call is related to data specific to the component (like specific user ID or name) you can handle the next call from the component. Instead of triggering the second API request from the component by watching for an event from the sibling, you can have the component watch the vuex store or data for a change. Then when $store.loadState becomes STATE.DONE, you can trigger another action for the other API call. I would only do this though if there is any part of the data that is specific to the API call, otherwise if the call comes right after in all circumstances, you might as-well call it as part of the same action

Vuex: getter to dispatch action if no data is in the state

I use vuex for my state as well as fetching data and display it in my application.
But I wonder if I'm doing it right. At the moment I dispatch an fetchDataAsync action from the component mounted hook, and I have an getter to display my data. Below is a code example of how I do it currently.
I wonder if it's necessary. What I really want is a getter, that looks at the state, checks if the data is already there and if the data is not there it is able to dispatch an action to fetch the missing data.
The API of vuex does not allow it so I need to put more logic into my components. E.g. if the data is depended of a prop I need a watcher that looks at the prop and dispatches the fetchDataAsync action.
For me it just feels wrong and I wonder if there is a better way.
let store = new Vuex.Store({
state: {
posts: {}
},
mutations: {
addPost(state, post) {
Vue.set(state.posts, post.id, post);
}
},
actions: {
fetchPostAsync({ commit }, parameter) {
setTimeout(
() =>
commit("addPost", { id: parameter, message: "got loaded asynchronous" }),
1000
);
}
},
getters: {
// is it somehow possible to detect: ob boy, I don't have this id,
// I'd better dispatch an action trying to fetch it...?
getPostById: (state) => (id) => state.posts[id]
}
});
new Vue({
el: "#app",
store,
template : "<div>{{ postToDisplay ? postToDisplay.message : 'loading...' }} </div>",
data() {
return {
parameter: "a"
};
},
computed: {
...Vuex.mapGetters(["getPostById"]),
postToDisplay() {
return this.getPostById(this.parameter);
}
},
methods: {
...Vuex.mapActions(["fetchPostAsync"])
},
mounted() {
this.fetchPostAsync(this.parameter);
}
});
I also created a codepen
Personally I think the solution you suggested (adding a watcher that dispatches fetchPostAsync if the post is not found) is the best one. As another commenter stated, getters should not have side effects.

Why declared field in data with props initial value is undefined?

Since mutating a prop is an antipattern I do the following as one of the solutions to that, however when I console.log my new data field I get undefined. What's wrong?
export default {
name: "modal",
props: ["show"],
data() {
return {
sent: false,
mutableShow: this.show
};
},
methods: {
closeModal: function() {
this.mutableShow = false;
},
sendTeam: function() {
var self = this;
let clientId = JSON.parse(localStorage.getItem("projectClient")).id;
axios({
method: "get",
url: "/send-project-team/" + clientId,
data: data
})
.then(function(response) {
self.sent = true;
$("h3").text("Wooo");
$(".modal-body").text("Team was sent succesfully to client");
setTimeout(function() {
console.log(this.mutableShow);
self.closeModal();
}, 3000);
})
.catch(function(error) {
console.log(error);
});
}
}
};
Your timeout handler is establishing a new context. Instead of
setTimeout(function() {
console.log(this.mutableShow);
self.closeModal();
}, 3000);
you could use
setTimeout(() => {
console.log(this.mutableShow);
self.closeModal();
}, 3000);
And you'd need to make a similar change to
.then(function(response) {
to
.then(response => {
having said that, though, I'm not sure the code is going to behave as you might want it. Once the users closes the modal, it won't be possible to open it again since there is no way to make mutableShow equal to true.
Edited to add:
Since you're defining the self variable, you could also use that.
console.log(self.mutableShow);
Edited to add:
Without knowing specifically what behavior is intended, the best suggestion I can offer is to follow accepted Vue practices. Namely, after the AJAX request succeeds, emit a custom event. Have the parent component listen for that event and, when triggered, change the show prop.