unable to render text to DOM from state vue 3 - vuejs3

I am trying to render a name to my component, which I get from an axios response. I am able to print the name in the console but {{username}} is never updated.
setup() {
const state = reactive({
username: '',
})
const submit = async () => {
try {
const response = await api.getTest()
if (response != null) {
state.username = response.name
console.log("I am the state " + state.username)
}
} catch (error) {
console.log('Error while getting the response:', error)
}
}
return {
...state,
submit
}
},
template
<template>
<button v-on:click="submit()" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
<div class="text-white">
Name: {{username}}
</div>
</template>
why is the username not updating?
is this the preferred way to do this?

You are using a reactive object, so you have to use that object in the template. username is not defined in the template scope, it would be {{state.username}}.
One other approach would be to define the username as a ref, but then you have to set it's value:
const username = ref('');
And in the async function:
username.value = response.name

Related

How to populate text-fields from pinia store state without changing the rendered values from other components?

hope you're well!
I have a Vue 3 app using Pinia + Vuetify 3. I've defined a "client" store and a component that, upon render, will call a store action that calls my backend API and sets my client state (JSON) with the result.
clientStore.js:
export const useClientStore = defineStore('clients', {
state: () => ({
//Loading state and client(s)
loading: false,
clients: [],
client: {}
}),
getters: {
//Get all clients
getClients(state) {
return state.clients
},
//Get one client
getClient(state) {
return state.client
}
},
actions: {
//Get one client
async fetchClient(clientId) {
try {
this.loading = true
const data = await axiosConfig.get('/clients/' + clientId)
this.client = data.data
this.loading = false
} catch (error) {
this.loading = false
console.log("Error fetching client: " + clientId)
},
//snipped
I have a computed property that returns the client from the store and render them as follows:
Component.vue:
<template>
<div class="text-center py-5">
<div class="text-h4 font-weight-bold">{{ client.name }}</div>
</div>
<div class="d-flex justify-space-between">
<div class="text-h5">Description</div>
<v-btn #click="dialog = true" prepend-icon="mdi-cog" color="primary">Edit</v-btn>
</div>
<v-textarea class="py-5" :value="client.description" readonly auto-grow outlined>{{ client.description
}}</v-textarea>
<updateClient v-model="dialog" />
</template>
<script setup>
import updateClient from '#/components/clients/updateClient.vue'
import { useClientStore } from '#/store/clients'
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'
const store = useClientStore()
const route = useRoute()
const dialog = ref(false)
const client = computed(() => {
return store.client
})
onMounted(() => {
store.fetchClient(route.params.clientId)
})
</script>
My aim is to make an "EDIT" component - a popup dialog - that takes the client state values and pre-populate them in my text fields and upon changing the values, submit and PATCH the client in the backend.
updateClient.vue
<template>
<v-dialog max-width="500">
<v-card class="pa-5">
<v-card-title>Edit client</v-card-title>
<v-text-field label="Name" v-model="client.name"></v-text-field>
<v-textarea label="Description" v-model="client.description"></v-textarea>
<v-btn block outlined color="primary" #click="updateClient">Update Client</v-btn>
</v-card>
</v-dialog>
</template>
<script setup>
import { useClientStore } from '#/store/clients'
import {computed} from 'vue'
const store = useClientStore()
const client = computed(() => {
return store.client
})
</script>
Problem is when I edit the pre-populated values in the fields, it changes the values outside the dialog as seen in the video and stay changed even after closing the pop-up. Ideally I'd like the values in my Component.vue to be static and have my state values unaltered. How can this be solved?
Thanks!
When you bind client.name to a text field in "Edit component", you directly change values stored in pinia. This, by design, changes values in your "View component".
A simple answer is... just create a copy of the object.
Now, I know, I know... there is a reason why you used computed properties in both places. Because you're waiting on the server to return the initial values.
The easiest way to solve this is to create a copy of the client object in pinia store. Then, just use copy of the object for text field binding in "Edit component".
state: () => ({
//Loading state and client(s)
loading: false,
clients: [],
client: {},
clientEdit: {} // Make changes to this object instead
})
In api response
actions: {
//Get one client
async fetchClient(clientId) {
try {
this.loading = true
const data = await axiosConfig.get('/clients/' + clientId)
this.client = data.data
this.clientEdit = { ...this.client } // Copy client object
this.loading = false
} catch (error) {
this.loading = false
console.log("Error fetching client: " + clientId)
},
}

Nextjs SendGrid Mailing Warning: List API resolved

I am trying to setup a Sendgrid Newletter signup, using the following code, stored in pages/api
import axios from "axios";
export default async function handler(req, res) {
if (req.method === "PUT") {
axios
.put(
"https://api.sendgrid.com/v3/marketing/contacts",
{
contacts: [{ email: `${req.body.mail}` }],
list_ids: [process.env.SENDGRID_MAILING_ID],
},
{
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
},
}
)
.then((result) => {
// return
res.status(200).send({
message:
"Your email has been succesfully added to the mailing list. Welcome 👋",
});
})
.catch((err) => {
// return
res.status(500).send({
message:
"Oups, there was a problem with your subscription, please try again or contact us",
});
});
}
}
The front end component looks similar to
import axios from "axios";
import { toast } from "react-toastify";
import { useState } from "react";
const MailingList = () => {
const [mail, setMail] = useState(null);
const [loading, setLoading] = useState(false);
const subscribe = () => {
setLoading(true);
axios
.put("api/mailingList", {
mail,
})
.then((result) => {
if (result.status === 200) {
toast.success(result.data.message);
setLoading(false);
}
})
.catch((err) => {
console.log(err);
setLoading(false);
});
};
return (
<div className='my-10'>
<hr className='my-5' />
<h2 className='text-3xl md:text-3xl font-semibold font-title'>
Stay Tuned!
</h2>
<label className='label'>
<p className='text-lg max-w-xl text-center m-auto leading-9'>
Want to be the first to know when SupaNexTail launches and get an
exclusive discount? Sign up for the newsletter!
</p>
</label>
<div className='mt-5'>
<input
onChange={(e) => {
setMail(e.target.value);
}}
type='email'
placeholder='Your email'
className='input input-primary input-bordered'></input>
<button
onClick={subscribe}
className={`btn ml-3 ${
loading ? "btn-disabled loading" : "btn-primary"
}`}>
I'm in!
</button>
</div>
<hr className='my-5' />
</div>
);
};
export default MailingList;
The emails are actually being added to the Sendgrid mailing list, but no response error is being displayed, email field is not cleared. And this is displayed in the console:
API resolved without sending a response for /api/MailingList, this may result in stalled requests.
The same console warning is displayed when return res.status(..
Need some advice on how to solve this!

Next js fetch API query

const Fetch = ({nationalize}) => {
return (
<div>
<h1>Nationalize</h1>
<h5>
<h4>You're {nationalize.name} and here's your results</h4>
{nationalize.country.map((i)=>{
return(
<div key={i.country_id}>
<h5>{(i.probability)*100}% {i.country_id} </h5>
</div>
)
})}
</h5>
</div>
);
}
export const getStaticProps = async (ctx) => {
const res = await fetch('https://api.nationalize.io?name=joe')
const nationalize = await res.json()
return {
props:{
nationalize,
}
}
}
export default Fetch;
So this is my NextJs page fetch API from nationalize.io, the API takes a name as a query. The code works just fine but I wanted to take the name query from an input field instead of being set manually. In this example it is Joe 'https://api.nationalize.io?name=joe', any suggestion? thank you
You use getStaticProps which pre-builds the page before sending it to your browser, so all data inside getStaticProps has to be static as the name says, and not being taken from any user input or query params.
If you'd like someone to change the name, you can either render it on the server using getServerSideProps and taking the name from the params, or fetch it on client-side via a user input as you mentioned:
import { useState } from "react";
const Fetch = () => {
const [name, setName] = useState("");
const [nationalize, setNationalize] = useState();
const submitName = async (name) => {
const res = await fetch("https://api.nationalize.io?name=" + name);
const nationalizeJson = await res.json();
setNationalize(nationalizeJson);
};
return (
<div>
<h1>Nationalize</h1>
{nationalize && (
<>
<h4>You're {nationalize.name} and here's your results</h4>
{nationalize.country.map((i) => {
return (
<div key={i.country_id}>
<h5>
{i.probability * 100}% {i.country_id}{" "}
</h5>
</div>
);
})}
</>
)}
<div>
<input
id="input"
type="text"
placeholder="Enter your name"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ padding: "10px" }}
/>
<button
onClick={() => {
if (!name) return;
submitName(name);
}}
>
Submit
</button>
</div>
</div>
);
};
export default Fetch;
Here's how it works:
Let user enter his name with a controlled text input (https://reactjs.org/docs/forms.html#controlled-components) and store the value in the state name
When a user clicks the "Submit" button, call the api as you did in getStaticProps but take the name from the state name, and store the returned JSON from the api in the state nationalize.
In the JSX part, check if nationalize is defined, and only then display the information fetched from the api.
This is obviously just a demo and could use some optimization like styling or a loading indicator.

How to use a method inside v-if

I'm trying to display countries from database which the continentId foreach country == logged user scope.
An user scope is between 1-5.
here is my vue template
<div class="container w-75" v-show="showGrid">
<div class="row" style="width:900px; height:900px; padding-left:200px">
<div class="col-md-4" v-for="country of countries" v-bind:key="country">
<div v-if="country.continentId==setup" class="card p-3" style="cursor:pointer">
<router-link :to="{ path: '/FetchData', query: { query: country.countryName}}">
<div class="d-flex flex-row mb-3">
<div class="d-flex flex-column ml-2"><span>{{country.countryId}}</span></div>
</div>
<h6 style="text-align:left">{{country.countryName}}</h6>
</router-link>
</div>
and those are my methods
export default {
methods: {
async getCountries() {
let country = this.$route.query.query
if (!country) {
await axios
.get("https://localhost:44391/api/Pho/GetCountries")
.then((res) => (this.countries = res.data))
} else {
await axios
.get(
"https://localhost:44391/api/Pho/GetCountries?country=" +
this.$route.query.query
)
.then((res) => (this.countries = res.data))
this.searchbar = false
}
},
async setup() {
let token = "??"
const scope = ref("")
const response = await fetch(
"https://localhost:44391/api/Auth/UserScope",
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
credentials: "include",
}
)
const content = response.json()
scope.value = `${content.scope}`
return {
scope,
}
},
},
async mounted() {
this.setup()
await this.getCountries()
},
}
the method setup return le scope of the logged user and the getCountries method returns the list of countries.
when i inspect i find that the scope is returned but the <div v-if="country.continentId==setup" class="card p-3" style="cursor:pointer"> condition does not work properly.
The setup() hook is not supposed to be under methods. It needs to be at the top level of the object:
export default {
methods: {
// setup() { /*...*/ } ❌
},
setup() { /*...*/ }, ✅
}
Also, don't make the setup() hook async unless you're intentionally making it an async component, which requires a <Suspense> as an ancestor. Instead, move the asynchronous code into its own function within:
export default {
setup() {
const scope = ref('')
const fetchUserScope = async () => {
const response = await fetch(/*...*/)
const content = await response.json()
scope.value = content.scope
}
fetchUserScope()
return { scope }
}
}
Also, you can't invoke the setup() hook from the template like that. You're really just trying to compare continentId to the scope value, so use scope directly:
<!-- <div v-if="country.continentId == setup"> --> ❌
<div v-if="country.continentId == scope"> ✅
You shouldn't try to invoke setup() from mounted() hook either. Vue controls the lifecycle hooks itself:
export default {
mounted() {
// this.setup() ❌
}
}

how to display spinner when data is being fetched from cloud firestore in vuejs?

i'm working on firebase and vuejs with vuex as well. so in onauthStateChanged() method i try to get all the data form posts collection. its takes some time to display, In meanwhile i want to display spinner that specifies the user where some something is being loading.
i tried and its works cool, but the problem with code is
<loadingSpinner v-if="loading"></loadingSpinner>
<div v-if="posts.length">
<div v-for="post in posts" v-bind:key=post.id class="post">
<h5>{{ post.userName }}</h5>
<span>{{ post.createdOn | formatDate }}</span>
<p>{{ post.content | trimLength }}</p>
<ul>
<li><a #click="openCommentModal(post)">comments {{ post.comments }}</a></li>
<li><a #click="likePost(post.id, post.likes)">likes {{ post.likes }}</a></li>
<li><a #click="viewPost(post)">view full post</a></li>
</ul>
</div>
</div>
<div v-else>
<p class="no-results">There are currently no posts</p>
</div>
Spinner component responsible for spin animation:
<loadingSpinner v-if="loading"></loadingSpinner>
And the below html code is for displaying data from firebase
Where posts and loading variables are the computed properties from vuex state
problem is when is reload the page, spinner showing along the
<div v-else>
<p class="no-results">There are currently no posts</p>
</div>
I want to restrict the v-else condition when the spinner is being loaded.
By the way, the loading computed properties is a boolean that reacts based on onAuthstateChanged() firebase method
this is my entire vuex store file :
import Vue from 'vue'
import Vuex from 'vuex'
const fb = require('./firebaseConfig.js')
Vue.use(Vuex)
// handle page reload
fb.auth.onAuthStateChanged(user => {
if (user) {
store.commit('setCurrentUser', user)
store.dispatch('fetchUserProfile')
fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
// realtime updates from our posts collection
fb.postsCollection.orderBy('createdOn', 'desc').onSnapshot(querySnapshot => {
// check if created by currentUser
let createdByCurrentUser
if (querySnapshot.docs.length) {
createdByCurrentUser = store.state.currentUser.uid == querySnapshot.docChanges[0].doc.data().userId ? true : false
}
// add new posts to hiddenPosts array after initial load
if (querySnapshot.docChanges.length !== querySnapshot.docs.length
&& querySnapshot.docChanges[0].type == 'added' && !createdByCurrentUser) {
let post = querySnapshot.docChanges[0].doc.data()
post.id = querySnapshot.docChanges[0].doc.id
store.commit('setHiddenPosts', post)
} else {
store.commit('setLoading', true)
let postsArray = []
querySnapshot.forEach(doc => {
let post = doc.data()
post.id = doc.id
postsArray.push(post)
})
store.commit('setPosts', postsArray)
store.commit('setLoading', false)
}
})
}
})
export const store = new Vuex.Store({
state: {
currentUser: null,
userProfile: {},
posts: [],
hiddenPosts: [],
loading: true
},
actions: {
clearData({ commit }) {
commit('setCurrentUser', null)
commit('setUserProfile', {})
commit('setPosts', null)
commit('setHiddenPosts', null)
},
fetchUserProfile({ commit, state }) {
fb.usersCollection.doc(state.currentUser.uid).get().then(res => {
commit('setUserProfile', res.data())
}).catch(err => {
console.log(err)
})
},
updateProfile({ commit, state }, data) {
let name = data.name
let title = data.title
fb.usersCollection.doc(state.currentUser.uid).update({ name, title }).then(user => {
// update all posts by user to reflect new name
fb.postsCollection.where('userId', '==', state.currentUser.uid).get().then(docs => {
docs.forEach(doc => {
fb.postsCollection.doc(doc.id).update({
userName: name
})
})
})
// update all comments by user to reflect new name
fb.commentsCollection.where('userId', '==', state.currentUser.uid).get().then(docs => {
docs.forEach(doc => {
fb.commentsCollection.doc(doc.id).update({
userName: name
})
})
})
}).catch(err => {
console.log(err)
})
}
},
mutations: {
setLoading(state, payload){
state.loading = payload
},
setCurrentUser(state, val) {
state.currentUser = val
// console.log(val)
},
setUserProfile(state, val) {
state.userProfile = val
// console.log(val)
},
setPosts(state, val) {
if (val) {
state.posts = val
} else {
state.posts = []
}
},
setHiddenPosts(state, val) {
if (val) {
// make sure not to add duplicates
if (!state.hiddenPosts.some(x => x.id === val.id)) {
state.hiddenPosts.unshift(val)
}
} else {
state.hiddenPosts = []
}
}
},
})
any suggestions?
I would tweak your v-if/v-else logic at bit.
<loadingSpinner v-if="loading" />
<div v-else-if="posts.length"></div>
<div v-else>
<p class="no-results">There are currently no posts</p>
</div>
The difference is v-else-if on posts.length, instead of v-if. This way, there are 3 distinct states.
Loading, show spinner.
Not loading, show posts.
Not loading, there are no posts, show no results.

Resources