I have backend Laravel API where i get
{
"id": 1,
"name": "Toni Stoyanov",
"email": "toni.nikolaev.23#gmail.com"
},
"id": 2,
"name": "Thomas Shelby",
"email": "st3851ba#gmail.com"
}
]
In my routes in Vue :
{
path: '/users/:id',
component: UserDetails,
props:true
},
I want to Display every single user for example with users/1 i want to get my first record from API.
In Vuex state i have :
namespaced: true,
state(){
return{
users: {
}
}
},
getters:
getUsers(state){
return state.users
}
mutations:
SET_USER(state, data){
state.users = data
}
and this action where i fetch all users:
async setUsers(context){
let response = await axios.get('users/all')
context.commit('SET_USER',response.data)
}
In my DisplayUser.vue i have :
props:['id'],
data(){
return{
selectedUser: null
}
},
created(){
this.$store.dispatch('users/setUsers')
this.selectedUser = this.$store.getters['users/getUsers'].find(user => user.id === this.id);
},
First i dispatch my action to get data from API and after that in selectedUsers try to find the same id user..but in console i get
this.$store.getters['users/getUsers'].find is not a function.
If i set in users static data everything works sometimes! But when i fetch them from API no.
You're trying to access the getter before the http request has completed. It should be enough to wait for the promise in created.
The prop is a string
Also, the prop is coming as a string, so a === will not match. You can cast it to a Number(this.id) first:
Using .then syntax:
created(){
this.$store.dispatch('users/setUsers').then(res => {
this.selectedUser = this.$store.getters['users/getUsers'].find(user => user.id === Number(this.id));
});
}
Or using async/await syntax:
async created(){ // async keyword
await this.$store.dispatch('users/setUsers') // await this
this.selectedUser = this.$store.getters['users/getUsers'].find(user => user.id === Number(this.id));
}
Related
My issue is that when i make a request to delete an item from my component, the component does not automatically update to show new state.
template
<div v-for="house in allHouses" :key="house.id" >
<div class="edit-delete-wrap" v-if="house.madeByMe">
<img class="edit-delete-btn" src="/images/edit.png" alt="">
<img #click="deleteHouse(house.id)" class="edit-delete-
btn" src="/images/delete.png" alt="" srcset="">
</div>
{{house.street}}
</div>
this is an example of the template, it is card with a house details on it, there is a button to delete this item from the list.
Scripts for house card component
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
name: "HouseCard",
props: ["searchHouses", "sortHouses"],
computed: {
...mapGetters(['allHouses']),
},
methods: {
...mapActions(['fetchHouses', 'houseDetail', 'deleteHouse']),
},
created(){
this.fetchHouses()
},
}
</script>
The list data comes from the allHouses houses computed function.
vuex store
import api from "../../api/houses";
const state = {
houses: [],
selectedHouse: [],
};
const getters = {
allHouses: (state) => state.houses,
selectedHouse: (state) => state.selectedHouse,
};
const actions = {
async fetchHouses({ commit }) {
const response = await api.fetchHouses();
commit("setHouses", response.data);
console.log(response.data);
},
createNewHouse(_, formData) {
api.createNewHouse(formData);
},
async deleteHouse(_, house) {
api.deleteHouse(house)
const response = await api.fetchHouses();
commit("setHouses", response.data);
},
async houseDetail({ commit }, id) {
const response = await api.fetchHouses();
response.data.forEach((house) => {
if (house.id === id) {
console.log(house);
commit("setSelectedHouse", house);
}
});
},
};
const mutations = {
setHouses: (state, houses) => {
state.houses = houses;
},
setSelectedHouse: (state, selectedHouse) => {
state.selectedHouse = selectedHouse;
},
};
export default {
state,
getters,
actions,
mutations,
};
here is the store where i have the manage the state of the app, in the deleteHouse action function i delete the house then try to get a new api response and set the state of houses to the new updated state of the houses array.
api
import axios from "axios";
const API_KEY = "xxxxxxxxx";
export default {
fetchHouses() {
return axios.get("https://api.intern.d-tt.nl/api/houses", {
headers: {
"X-Api-Key": API_KEY,
},
});
},
async deleteHouse(id) {
axios
.delete(`https://api.intern.d-tt.nl/api/houses/${id}`, {
headers: {
"X-Api-Key": API_KEY,
},
})
.then(() => {
console.log("successful deletion");
});
},
createNewHouse(formData) {
console.log("api page", formData);
const image = formData.houseImage;
return axios
.post("https://api.intern.d-tt.nl/api/houses", formData.form, {
headers: {
"X-Api-Key": API_KEY,
},
})
.then((res) => {
console.log("REACHED FIRST POST");
const id = res.data.id;
const formData = new FormData();
formData.append("image", image[0]);
axios
.post(
`https://api.intern.d-tt.nl/api/houses/${id}/upload`,
formData,
{
headers: {
"X-Api-Key": API_KEY,
},
}
)
.then(console.log("success"))
.catch((err) => console.log(err));
})
.catch((err) => {
console.log(err);
});
},
};
here is the api file that i use to make all api requests. i hope this information helps.
Per #RenauldC5, you didn't provide enough information about your allHouses getter or where and how the data gets into the store or where setHouses stores the data, and how.
However a question/tips to guide you:
#setHouses - I presume your api returns an array .. (?) So make sure you initialize the property (key) in the store (at whatever key #setHouses stores this array) at instantiation so that the store and Vue instances that use the allHouses getter know to watch that array property
#deleteHouses - When you delete the array, you seem to return a new array from the api. If this is true, simply setting the new non-reactive array overtop of the old reactive one will create an unreactive condition. This is a fundamental understanding of Vue's reactivity system - and is likely the cause of your problem.
Fixes:
Whatever key #setHouses uses to set data on the Vuex store, instantiate it as an empty array when the store is created.
#setHouses must iterate response.data and array.push(item) onto this reactive array, rather than simply replace the reactive array with a new (non-reactive) array.
#deleteHouse - should first use array.splice(0) to remove all children in the reactive array, then setHouses will array.push(child) into this reactive array
Update: including examples
//- update: state:
const state = {
houses: [],
selectedHouse: null,
};
//- update: #setHouses
setHouses: (state, houses) => {
// empty the previous reactive array
state.houses.splice(0);
// push the new houses to the original reactive array
state.houses.push(...houses);
// state.houses now remains bound to your getters, vue instances and remains reactive
},
Update: add examples of changes
//- update:state
const state = {
houses: [],
selectedHouse: null,
};
//- update:#setHouses
setHouses: (state, houses) => {
// empty the previous reactive array
state.houses.splice(0);
// push the new houses to the original reactive array
state.houses.push(...houses);
// state.houses now remains bound to your getters, vue instances and remains reactive
},
PS: maybe I'm not clear on what your action #houseDetail does but it seems to re-load ALL houses ... perhaps this is what you want (?)
I have some data from an API that I am storing in VUEX and then displaying in the UI. On initial page load there is a request that pulls in the initial data and displays. All works well. My issues is When I now submit a form input for another request using an event handler I am just pushing to the array and it is adding to the array (which makes sense) and creates another instance below the current data which I do not want. Is there a way to actually CHANGE / MUTATE the current data that is in the array and update the UI with the new values?
STORE
import { createStore } from 'vuex';
import axios from 'axios';
export default createStore({
state: {
ipData: [],
currentIP: '',
},
mutations: {
SET_CURRENT_IP(state, currentIP) {
state.currentIP = currentIP;
},
SET_IP_DATA(state, ipData) {
state.ipData.push(ipData);
},
},
});
FORM SUBMIT
methods: {
async submitForm() {
const isFormValid = await this.v$.$validate();
if (!isFormValid) return;
axios
.get(`${this.url}${this.getIP}`, {
headers,
})
.then((response) => {
this.$store.commit('SET_IP_DATA', response.data);
})
.catch((error) => {
console.log(error.response);
});
},
},
VUEX OBJECT:
ipData:Array[1]
0:Object
as:Object
domains:Array[5]
ip:"8.8.8.8"
isp:"Google LLC"
location:Object
city:"Mountain View"
country:"US"
geonameId:5375480
lat:37.38605
lng:-122.08385
postalCode:"94035"
region:"California"
timezone:"-07:00"
If your ipData is array of object, you can create another mutation for updating your array (use id or some other identifier to match right object):
UPDATE_IP_DATA(state, payload) {
state.ipData = [
...state.ipData.map((item) =>
item.id !== payload.id
? item
: {
...item,
...payload,
}
),
];
}
I have several different queries that I need to keep 'live' data from. When I hardcode the query it successfully shows all live changes that happen in the database. But when I pass a payload the data won't change until reloaded.
Working Query:
getOnline: firestoreAction(({ bindFirestoreRef }) => {
return bindFirestoreRef('online', db.collection('units').where('unit.on', '==', true).orderBy('info.timestamp', 'desc').orderBy('unit.status'))
}),
Not working Payload Query: gets data once
getFilterSystemId: firestoreAction(({ bindFirestoreRef} , payload) => {
return bindFirestoreRef('filterBySystemId', db.collection('units').where('unit.group', '==', payload.group).orderBy('info.timestamp', 'desc').orderBy('unit.status'))
}),
I pass the payload simply:
filterSystemId(grouphex){
let payload = {
group: grouphex.toString(),
}
this.getFilterSystemId(payload);
},
How do I pass a payload AND get live updates to any changes that happen in the database?
Ended up using vuefire instead of vuexfire and dynamically binding my queries like this.
const vuefire = db.collection('vuefire')
export default {
components: {},
data() {
return {
//view
vuefire: [],
id: 'true',
user: [],
code: 'true'
};
},
created() {
},
// firestore: {
// vuefire: db.collection('vuefire')
// }
watch: {
id: {
// call it upon creation too
immediate: true,
handler(id) {
this.$bind('user', vuefire.where('a', '==', id))
},
},
Anytime 'id' changes the dataset ('user') is recomputed and accomplishes my goal
Undefined error when trying to read information from a JSON object in 'this.state'.
Component Class:
class App extends Component {
state = {}
// Code is invoked after the component is mounted/inserted into the DOM tree.
componentDidMount() {
const url = 'http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/APIKEY'
fetch(url)
.then(response => {return response.json()})
.then(data => {
this.setState(data)
})
}
render() {
console.log(this.state.SiteRep.DV)
return <p>TEXT</p>
}
}
export default App
Json Object:
{
"SiteRep": {
"Wx": {
"Param": [
{
"name": "FDm",
"units": "C",
"$": "Feels Like Day Maximum Temperature"
},
{
"name": "FNm",
"units": "C",
"$": "Feels Like Night Minimum Temperature"
},
...
]
},
"DV": {
"dataDate": "2019-10-09T13:00:00Z",
"type": "Forecast",
"Location": {
"i": "354287",
"lat": "52.0951",
"lon": "1.3143",
"name": "WOODBRIDGE",
"Period": [
{
"type": "Day",
"value": "2019-10-09Z",
"Rep": [
{
"D": "W",
"Gn": "22",
"Hn": "66",
"PPd": "8",
...
},
{
"D": "WSW",
"Gm": "22",
"Hm": "87",
"PPn": "1"
}
]
},
{
"type": "Day",
"value": "2019-10-10Z",
"Rep": [
{
"D": "WSW",
"Gn": "29",
"Hn": "61",
"PPd": "5",
},
{
"D": "SW",
"Gm": "34",
"Hm": "87",
"PPn": "19",
}
]
}
...
]
}
}
}
}
I am going to map the "Period" list to map some elements, however when trying to access said part of the Object in 'this.state', I am greeted with the error 'Cannot read property 'DV' of undefined'
The object is found within state, all the Json data is correct and I can access it in Console when I am not beyond the '.SiteRep' object. Feel like I am doing something very wrong :D
The issue here is, although you get your Json in componentDidMount, render() happens before fetch is completed. Just by checking whether data is available before accessing it would solve your issue.
class App extends Component {
state = {}
// Code is invoked after the component is mounted/inserted into the DOM tree.
componentDidMount() {
const url = 'http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/APIKEY'
fetch(url)
.then(response => {return response.json()})
.then(data => {
this.setState(data)
})
}
render() {
if(this.state.SiteRep){
console.log(this.state.SiteRep.DV);
}
return <p>TEXT</p>
}
}
export default App
This is a lifecycle issue. ComponentDidMount triggers after the component has loaded, which means this.state.SiteRep.DV does not exist yet. You'll need to add a check to see if SitRep exists on state so it doesn't error out.
So...
if(this.state.hasOwnProperty('SiteRep') console.log(this.state.SiteRep.DV)
Here's the expected behavior of the component:
First render happens
=> `ComponentDidMount` is fired
=> Request is made and response is set to state
=> setState triggers another render where SiteRep does exist on state
Then for your JSX:
render() {
if(this.state.SiteRep && this.state.SiteRep.DV) {
return <p>interpolate a value from {this.state.SiteRep.DV.someStringProp} </p>
}
return null
}
Please consider going through React's Component lifecycle one more time here.
This is what's happening in your code:
Your component state is initialized to empty object.
Component's render() method is called (where you don't have the data you need).
Lifecycle method componentDidMount() is called after initial render(), where you make an API call and update the state with response data.
One way to solve this -
You can consider having a flag isFetching in your state which is set to true initially.
state = { isFetching: true };
Update your render() method to display a fallback UI until your API call is successful i.e. isFetching is false;
...
render() {
const { isFetching } = this.state;
if (isFetching) {
return <span> Fetching data </span>;
}
console.log(this.state.SiteRep.DV)
return <p>TEXT</p>
}
...
Add changes to update your isFetching flag when API call is being made:
...
componentDidMount() {
// You're going to call the API now, set isFetching to true
this.setState({ isFetching: true });
const url = 'http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/APIKEY'
fetch(url)
.then(response => {return response.json()})
.then(data => {
// API call done, set data and make isFetching false.
this.setState({ data, isFetching: false });
})
}
...
Another approach would be to add the initial state such as:
state = {
SiteRep: {
DV: {}
}
}
With this approach, initially you would see no data but once API response is updated in your component state, component will re-render.
Personally I'd take the first approach as you can have finer control over your render()
My Vue app has the following state
saving: false,
saveStatus: 0,
validationErrors: [],
record: {
createdAt:"2017-04-22T16:17:14.825Z"
email:"isreyakenobi#starwars.com.au"
id:"BFCD7EC9-094D-4A97-BDFD-5C7D9D9F092A"
mobile:"12345678"
name:"Rey QuestionMark"
phone:"94801234"
type:"Customer"
updatedAt:"2017-04-25T18:38:21.101Z"
}
I have a component which fires the saveCustomer action when a user completed a form updating the customer's email.
saveCustomer = async ({ commit, state }, customer) => {
commit(types.SAVE_CUSTOMER)
const response = await api.put(
'customers',
customer
)
if (response instanceof Error) {
// If parameters passed are invalid
if (response.response.status === config.errorStatusCodes.badRequest) {
commit(types.SAVE_CUSTOMER_INVALID, Object.values(response.response.data.message))
return
}
commit(types.SAVE_CUSTOMER_FAILURE)
return
}
commit(types.CUSTOMER_SAVED, response.data)
commit(types.CANCEL_EDIT_CUSTOMER)
}
Upon the api returning a Bad Request response as so
{
"statusCode":"400",
"statusText":"Bad Request",
"name":"",
"message":{
"email":"\"email\" is not correctly formatted."
}
}
The SAVE_CUSTOMER_INVALID mutation does not touch the record in the state but rather mutates other aspects of the state.
[types.SAVE_CUSTOMER_INVALID] (state, errors) {
state.saving = false
state.saveStatus = config.saveStatuses.invalid
state.validationErrors = errors
}
For some reason the id key value pair is stripped from the record in the state when this mutation is committed. Every other key value pair is persisted.
Any ideas what's going on?