I've been trying to make a Vue3 app work with SSR and I'm stuck in the serverPrefetch part. I want to load some data in the server, save it in the store and pass it to the client but it looks like the rendered hook referred to here: https://ssr.vuejs.org/guide/data.html#final-state-injection is not called anymore (and therefore the server store is not updated after the data fetch). Do you know what the right way to do this with Vue3 is?
This is my server-entry.js
import { _createApp } from "./app";
export default async (context) => {
const { app, router, store } = _createApp();
router.push(context.url); // push router entry
await router.isReady();
context.rendered = () => {
// this is never executed!!!
context.state = store.state; // this gets embedded in the final HTML as window.__INITAL_STATE__
};
return app;
};
Related
I am working with Vue 3 composition api and am retrieving weather data via async/await fetch and I get a 200 response and the data in the request within the Chrome Dev Tools.
In the component receiving the data and making the call I have a provide method and then I am injecting the data into another output component. The issue is in the inject component. The value for the injected variable is always null and does not update in the Vue Dev Tools so my data is never output to the screen. I went through the docs and the code is pretty much the same but I can't get it to work. Can anyone see an obvious issue?
Receiving Component
setup () {
async function getCurrentWeather () {
const response = await fetch(`${baseWeatherApiUrl}q=${userInput.value}`);
userInput.value = null;
return weatherData.value = await response.json();
}
const returnedWeatherData = reactive(weatherData);
provide('returnedWeatherData', returnedWeatherData);
return {
getCurrentWeather,
userInput,
weatherData
}
}
output component
setup () {
//Provide default of empty object in case no results exist
const weatherData = inject('returnedWeatherData');
console.log(weatherData) //No output even when making a new request to the weather api
return {
weatherData
}
}
As a separate test I tried to provide/inject hardcoded values found in the docs but still geolocation when injected remains null.
provide('geolocation', {
longitude: 90,
latitude: 135
})
const userGeolocation = inject('geolocation')
console.log(userGeolocation) // Nothing logged
return {
weatherData,
userGeolocation
}
In my case it was importing inject from "#vue/runtime-core" instead of "vue".
Of course provide was imported from "vue".
Just leaving here, maybe it's gonna save someone an hour.
The provide-ed argument should be the ref itself (not wrapped in a reactive()):
// Parent.vue
export default {
setup () {
const weatherData = ref()
// ❌
// const returnedWeatherData = reactive(weatherData);
// provide('returnedWeatherData', returnedWeatherData);
// ✅
provide('returnedWeatherData', weatherData);
}
}
And the child component's console.log() in setup() does not automatically get invoked again. You should wrap that call with watchEffect() so that it does get called upon change to the ref:
// Child.vue
import { inject, watchEffect } from 'vue'
export default {
setup () {
const weatherData = inject('returnedWeatherData')
// ❌
//console.log('new weatherData', weatherData.value)
// ✅
watchEffect(() => {
console.log('new weatherData', weatherData.value)
})
}
}
demo
// main.js
const app = createApp(App);
app.provide('$axios', axios);
window.renderSomething = function() {
app.mount('#ticker-maintenance-host');
}
the application is developed with vuejs and blarzor. In blazor, will call it
#inject IJSRuntime JS;
#code{
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("renderSomething");
}
}
}
#page "/dashboard/page1"
<div id="ticker-maintenance-host"></div>
first open this page1, all the content can show up. If I leave it and come back to this page, nothing show up. There is a warning saying
"[Vue warn]: App has already been mounted.
If you want to remount the same app, move your app creation logic into a factory function and create fresh app instances for each mount - e.g. `const createMyApp = () => createApp(App)`".
How can I fix this warning and make my content show up without refresh when coming back.
Thanks
You are mounting your App to the ID #ticker-maintenance-host
But all I can see in your HTML is an ID container.
Mount your app to the container, or add the ID #ticker-maintenance-host to your HTML.
#Edit:
You dont need to mount your view app on every render, just when the website is loaded initially.
Something like this straight out of the docs for Vue3 is how you register a vue app:
const { createApp, ref, computed } = Vue;
const app = createApp({
setup() {
const someValue = ref(10);
const someComputed = computed(() => someValue.value * 10);
return {
someValue,
someComputed
}
}
});
app.mount("#ticker-maintenance-host");
More reading here: Vue.createApp is not working but Is working with new Vue() method
I have fixed it by moving all the statements inside function block.
// main.js
window.renderSomething = function() {
const app = createApp(App);
app.provide('$axios', axios);
app.mount('#ticker-maintenance-host');
}
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 :)
I'm working on a Vue app which also uses vuex.
Everything is setup ad working correctly as expected but i'd like to improve it so that I can work on it without actually calling the API endpoints (mainly to avoid rate limit).
I created a mock folder and placed some file in there.
How do I manage to use those mock in development, and the real api endpoint on the build on production withouth making a mess in my code ?
I created a repo with as less as possible.
It includes vue + vuex, a single smart component in charge of reading from the store, and a dumb component do display it.
In poor words, I'm looking for a way to do change this:
const actions = {
async fetchTodos({ commit }) {
let response;
if (process.env.NODE_ENV === "development") {
response = { data: todos };
} else {
response = await axios.get("https://jsonplaceholder.typicode.com/todos");
}
commit("setTodos", response.data);
}
};
with something which would be easier to maintain and wouldn't increase the bundle size.
I thought about mocking the whole action object, which seemed to be ok, but how do i avoid to bundle my mock files at that point?
How do you manage your front end environment to avoid this kind of problem?
What I did is encapsulate the whole API in another class/object. That single point of entry then switches between the mock and real api:
// store.js
const api = require('./api');
const actions = {
async fetchTodos({ commit }) {
// you can use api.getTodos() instead or another naming convention
const response = await api.get('todos');
commit("setTodos", response.data);
},
};
// api.js
const realapi = require('./realapi');
const mockapi = require('./mockapi');
module.exports = process.env.NODE_ENV === 'production' ? realapi : mockapi;
// mockapi/index.js
const todos = loadTodos();
module.exports = {
async get(path) {
switch (path) {
case 'todos':
return { data: todos };
// etc.
}
}
};
// realapi/index.js
const root = 'https://jsonplaceholder.typicode.com/';
module.exports = {
get(path) {
return axios.get(root + path);
}
};
Builders like Webpack can optimize the build and remove the whole mock api part in production builds based on the environment.
I have an application in nuxt that I want to connect to a websocket, I have seen examples where the callback to receive messages is placed inside a component, but I do not think ideal, I would like to place the callback inside my store, currently my code is something like this
//I'm using phoenix websocket
var ROOT_SOCKET = `wss://${URL}/socket`;
var socket = new Socket(ROOT_SOCKET);
socket.connect()
var chan = socket.channel(`connect:${guid}`);
chan.join();
console.log("esperando mensj");
chan.on("translate", payload => {
console.log(JSON.stringify(payload));
<store>.commit("loadTranslation",payload) //<- how can I access to my store?
})
chan.onError(err => console.log(`ERROR connecting!!! ${err}`));
const createStore = () => {
return new Vuex.Store({
state: {},
mutations:{
loadTranslation(state,payload){...}
},
....
})}
how can I access to my store inside my own store file and make a commit??? is it possible?...
I know there is a vuex plugin but I can't really understand well the documentation and I'll prefer build this without that plugin
https://vuex.vuejs.org/guide/plugins.html
thank you guys...hope you can help me...
You can do it in nuxt plugin https://nuxtjs.org/guide/plugins/
export default {
plugins: ['~/plugins/chat.js']
}
// chat.js
export default ({ store }) => {
your code that use store here
}