How can I get the data from Strapi backend to display on the front end - vue.js

I have a strapi backend platform with a Vue front end. I'm trying to display content on the front page, but I'm having an issue.
It's calling in the data no problem and displays it in the console.log when I put one in. However, in the Vue data, the array remains empty so it won't display the data at all. Is there something I'm missing in my script on the page I want it to display? Below is the specific script code.
Again, the return within the try/catch actually shows the data. It's the return in the Vue data that doesn't want to populate
<script>
import Vue from "vue";
export default {
name: "whatsNew",
async beforeRouteEnter(to, from, next) {
try {
var WhatsNew = await Vue.$whatsNewService.findOne(to.params.id);
return next((vm) => {
vm.WhatsNew = WhatsNew;
console.log(WhatsNew.title);
});
} catch (err) {
console.log(err);
next(false);
}
},
data() {
return {
whatsNew: [],
};
},
};
</script>

For this instance, everything was right aside from a major gaffe...capitlization of whatsNew in the return. WhatsNew solved it

Related

How to use highlight.js in a VueJS app with mixed content

I'm currently using highlight.js to hightlight the code in the HTML content being received from my backend. An example of something I might receive from the backend is:
<h3> Check this example of Javascript </h3>
<pre>
<code class="language-javascript">let x = 0;
function hello() {}
</code>
</pre>
As you can see it is a mixed content of HTML and code examples wrapped in pre -> code tags.
I have a component to render WYSIWYG content returned from the backend. In this component, I use highlight.js to highlight the code blocks.
import { defineComponent, h, nextTick, onMounted, ref, watch } from 'vue';
// No need to use a third-party component to highlight code
// since the `#tiptap/extension-code-block-lowlight` library has highlight as a dependency
import highlight from 'highlight.js'
import { QNoSsr } from 'quasar';
export const WYSIWYG = defineComponent({
name: 'WYSIWYG',
props: {
content: { type: String, required: true },
},
setup(props) {
const root = ref<HTMLElement>(null);
const hightlightCodes = async () => {
if (process.env.CLIENT) {
await nextTick();
root.value?.querySelectorAll('pre code').forEach((el: HTMLElement) => {
highlight.highlightElement(el as HTMLElement);
});
}
}
onMounted(hightlightCodes);
watch(() => props.content, hightlightCodes);
return function render() {
return h(QNoSsr, {
placeholder: 'Loading...',
}, () => h('div', {
class: 'WYSIWYG',
ref: root,
innerHTML: props.content
}));
};
},
});
Whenever I visit the page by clicking on a link the page works just fine, but when I hard refresh the page I get the following error:
`line` must be greater than 0 (lines start at line 1)
Currently, I'm not sure precisely why this happens, and tried a couple of different approaches
Aproach 1: try to build the whole content and then replace
const computedHtml = computed(() => {
if (import.meta.env.SSR) return '';
console.log(props.content);
const { value } = highlight.highlightAuto(props.content);
console.log(value);
return '';
})
With this approach, I get the same error as before
`line` must be greater than 0 (lines start at line 1)
I have checked out this error in https://github.com/withastro/astro/issues/3447 and https://github.com/vitejs/vite/issues/11037 but it looks like that this error is more related to Vite than my application - please, correct me if I'm wrong here.
Is there a way for me to highlight the code in the backend that is being returned from the backend in Vue?

How do I use Vuex store to update a user-specific element in a child component?

I have many components and quite a long code, but I will try to keep it simple without bombing you with a mountain of code.
I have a page where logged in users can create profile images. Each user has field in the database called profilePictureFileResourceId
The image is stored in it's own component called FileResourceImage, which returns that image to the profilePictureFileResourceId.
So for example. When I display the image on the user profile page. I do it like this:
<template>
<file-resource-image v-model="user.profilePictureFileResourceId" />
</template>
<script>
import { userService } from "#/services/user";
import FileResourceImage from "#/components/FileResourceImage";
export default {
components: {
FileResourceImage,
},
data() {
return {
user: {
profilePictureFileResourceId: null,
email: "",
name: "",
},
};
},
};
</script>
Then I have a navbar where I also show the profile picture.
<b-nav-item>
<file-resource-image
v-model="user.profilePictureFileResourceId"
class="profile-image"
default-icon="fas fa-user"
/>
</b-nav-item>
<script>
import { userService } from "#/services/user";
import FileResourceImage from "#/components/FileResourceImage";
export default {
components: {
FileResourceImage,
},
data() {
return {
user: {},
};
},
};
</script>
My issue is that whenever a user uploads an image to their profile page, they need to refresh the browser in order for the image to appear in the navbar. I would like the navbar component to automatically update and show the image when it is uploaded to the profile page. I have read about vuex and stored procedure, but the examples shown in the documentation are very simple and only focus on click events. So I don't know where to begin.
I only set up the basics.
const store = createStore({
state() {
return {
profilePictureFileResourceId: null,
};
},
});
I'm assuming the interaction would happen like this: The user POSTs new image, and then response would include the new image name (or return success, and then another API calls the newly updated data). If you have the new image name in the response, you should be able to update it in the store.
It would look something like this (if the new id was in the response already and doesn't require an additional request)...
const store = createStore({
state: {
profilePictureFileResourceId: null
},
mutations: {
setNewImage (state. newId) {
state.profilePictureFileResourceId = newId
}
},
actions: {
updateProfileMug(context, imageFile) {
let formData = new FormData();
formData.append('file', imageFile);
axios.post(
'/single-file',
formData,
{ headers: {'Content-Type': 'multipart/form-data'} }
).then(({data})=>{
// assuming profilePictureFileResourceId is in the response data
context.commit('setNewImage', data.profilePictureFileResourceId)
})
}
}
})
to call, use either mapActions('updateProfileMug') and this.updateProfileMug(image) or store.dispatch('updateProfileMug', image) from the component.
Note that the state inside createStore is not a function, but just a plain object.
Also note that if image name is the same the browser doesn't know that it should load the new image. If that is the case, you can use use the timestamp hack ...jpg?ts=1658940428 which will force the browser to reload the resource. Note that this assumes that the reactivity is triggered correctly after an update.

vue/vuex: Can you re-render a page from another page?

With the first login in my app, users get a possibility to leave their address. When this address is stored, the user are pushed to their dashboard. Second login the user go straight to the dashboard.
I have 2 Vuex states that are updated with the response.data. 'Signed' leads to address page, 'Frequent' leads to 'dashboard'.
//PROMPT.VUE
mounted () {
this.getPrompt()
},
computed: {
promptStatus () {
return this.$store.getters.getPrompt
}
},
methods: {
async getPrompt() {
try{
await //GET axios etc
// push prompt status in Store
let value = response.data
this.$store.commit('setPrompt', value)
if (this.promptStatus === 'signed') {
this.$router.push({path: '/adres'})
}
if (this.promptStatus === 'frequent') {
this.$router.push({path: '/dashboard'})
}
When user leaves the address I reset the vuex.state from 'signed' to 'frequent'.
//ADRES.VUE
//store address
let value = 'frequent'
this.$store.commit('setPrompt', value)
this.$router.push({name: 'Prompt'})
The Vuex.store is refreshed. But the Prompt.vue wil not re-render with the new vuex.status. Many articles are written. Can 't find my solution. Maybe I organize my pages the wrong way.
In views, it is not recommended to mutate data (call commit) outside vuex. Actions are created for these purposes (called from the component using dispatch). In your case, you need to call action "getPrompt" from the store, but process routing in the authorization component. This is more about best practice
To solve your problem, you need to make a loader when switching to dashboard. Until the data is received, you do not transfer the user to the dashboard page
Example
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "DashboardLayout",
components: { ..., ... },
data: () => ({
isLoad: false
}),
async created() {
this.isLoad = false;
try {
await this.$store.dispatch('getData');
this.isLoad = true;
} catch (error) {
console.log(error)
}
}
});
</script>
Data is received and stored in the store in the "getData" action.
The referral to the dashboard page takes place after authorization. If authorization is invalid, the router.beforeEach handler (navigation guards) in your router/index.js should redirect back to the login page.
Learn more about layout in vuejs
Learn more about navigation guards

Vuex populate data from API call at the start

apologies for the simple question, I'm really new to Vue/Nuxt/Vuex.
I am currently having a vuex store, I wish to be able to populate the list with an API call at the beginning (so that I would be able to access it on all pages of my app directly from the store vs instantiating it within a component).
store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, testArray) {
state.list = testArray
}
}
export const getters = {
getArray: state => {
return state.list
},
}
I essentially want to pre-populate state.list so that my components can call the data directly from vuex store. This would look something like that
db.collection("test").doc("test").get().then(doc=> {
let data = doc.data();
let array = data.array; // get array from API call
setListAsArray(); // put the array result into the list
});
I am looking for where to put this code (I assume inside store.js) and how to go about chaining this with the export. Thanks a lot in advance and sorry if it's a simple question.
(Edit) Context:
So why I am looking for this solution was because I used to commit the data (from the API call) to the store inside one of my Vue components - index.vue from my main page. This means that my data was initialized on this component, and if i go straight to another route, my data will not be available there.
This means: http://localhost:3000/ will have the data, if I routed to http://localhost:3000/test it will also have the data, BUT if i directly went straight to http://localhost:3000/test from a new window it will NOT have the data.
EDIT2:
Tried the suggestion with nuxtServerInit
Updated store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, dealArray) {
state.list = dealArray
}
}
export const getters = {
allDeals: state => {
return state.list
},
}
export const actions = {
async nuxtServerInit({ commit }, { req }) {
// fetch your backend
const db = require("~/plugins/firebase.js").db;
let doc = await db.collection("test").doc("test").get();
let data = doc.data();
console.log("deals_array: ", data.deals_array); // nothing logged
commit('set', data.deals_array); // doesn't work
commit('deals/set', data.deals_array); // doesn't work
}
}
Tried actions with nuxtServerInit, but when logging store in another component it is an empty array. I tried to log the store in another component (while trying to access it), I got the following:
store.state: {
deals: {
list: []
}
}
I would suggest to either:
calling the fetch method in the default.vue layout or any page
use the nuxtServerInit action inside the store directly
fetch method
You can use the fetch method either in the default.vue layout where it is called every time for each page that is using the layout. Or define the fetch method on separate pages if you want to load specific data for individual pages.
<script>
export default {
data () {
return {}
},
async fetch ({store}) {
// fetch your backend
var list = await $axios.get("http://localhost:8000/list");
store.commit("set", list);
},
}
</script>
You can read more regarding the fetch method in the nuxtjs docs here
use the nuxtServerInit action inside the store directly
In your store.js add a new action:
import axios from 'axios';
actions: {
nuxtServerInit ({ commit }, { req }) {
// fetch your backend
var list = await axios.get("http://localhost:8000/list");
commit('set', list);
}
}
}
You can read more regarding the fetch method in the nuxtjs docs here
Hope this helps :)

Transfer Data From One Component to Another

I have a component which makes a call to my backend API. This then provides me with data that I use for the component. I now want to create another component which also uses that data. While I could just do another api call that seems wasteful.
So, in Profile.vue i have this in the created() function.
<script>
import axios from 'axios';
import { bus } from '../main';
export default {
name: 'Profile',
data() {
return {
loading: false,
error: null,
profileData: null,
getImageUrl: function(id) {
return `http://ddragon.leagueoflegends.com/cdn/9.16.1/img/profileicon/` + id + `.png`;
}
}
},
beforeCreate() {
//Add OR Remove classes and images etc..
},
async created() {
//Once page is loaded do this
this.loading = true;
try {
const response = await axios.get(`/api/profile/${this.$route.params.platform}/${this.$route.params.name}`);
this.profileData = response.data;
this.loading = false;
bus.$emit('profileData', this.profileData)
} catch (error) {
this.loading = false;
this.error = error.response.data.message;
}
}
};
</script>
I then have another child component that I've hooked up using the Vue router, this is to display further information.
MatchHistory compontent
<template>
<section>
<h1>{{profileDatas.profileDatas}}</h1>
</section>
</template>
<script>
import { bus } from '../main';
export default {
name: 'MatchHistory',
data() {
return {
profileDatas: null
}
},
beforeCreate() {
//Add OR Remove classes and images etc..
},
async created() {
bus.$on('profileData', obj => {
this.profileDatas = obj;
});
}
};
</script>
So, I want to take the info and display the data that I have transferred across.
My assumption is based on the fact that these components are defined for two separate routes and an event bus may not work for your situation based on the design of your application. There are several ways to solve this. Two of them listed below.
Vuex (for Vue state management)
Any local storage option - LocalStorage/SessionStorage/IndexDB e.t.c
for more information on VueX, visit https://vuex.vuejs.org/.
for more information on Localstorage, visit https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage.
for more information on session storage, visit https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
The flow is pretty much the same for any of the options.
Get your data from an API using axios as you did above in Profile.vue
Store the retrieved data with VueX or Local/Session storage
Retrieve the data from Vuex or local/session storage in the created method of MatchHistory.vue component
For the local / session storage options, you will have to convert your object to a json string as only strings can be stored in storage. see below.
in Profile.vue (created)
const response = await axios.get(........)
if(response){
localStorage.setItem('yourstoragekey', JSON.stringify(response));
}
In MatchHistory.Vue (created)
async created() {
var profileData = localStorage.getItem('yourstoragekey')
if(profileData){
profileData = JSON.parse(profileData );
this.profileData = profileData
}
}
You can use vm.$emit to create an Eventbus
// split instance
const EventBus = new Vue({})
class IApp extends Vue {}
IApp.mixin({
beforeCreate: function(){
this.EventBus = EventBus
}
})
const App = new IApp({
created(){
this.EventBus.$on('from-mounted', console.log)
},
mounted(){
this.EventBus.$emit('from-mounted', 'Its a me! Mounted')
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
further readings
You can make use of the VUEX which is a state management system for Vue.
When you make api call and get the data you need, you can COMMIT a MUTATION and pass your data to it. What it will do, it will update your STATE and all of your components will have access to its state (data)
In your async created(), when you get response, just commit mutation to your store in order to update the state. (omitted example here as the vuex store will need configuration before it can perform mutations)
Then in your child component,
data(){
return {
profileDatas: null
}
},
async created() {
this.profileDatas = $store.state.myData;
}
It might seem like an overkill in your case, but this approach is highly beneficial when working with external data that needs to be shared across multiple components