nuxtjs : how to force page reload and call asyncData()? - javascript

In my NuxtJS SSR project with bootstrap-vue as frontend :
I have some page with template and default component
In component there is asyncData(context) function that makes some deal before component render and mounts
Everything is working fine, except only one thing, I need reload (or refresh) a page after some interval automatically and call asyncData again. Standard js window.location.reload() is not good solution, because it`s reloads fully page. I want refresh in vue style, when only changed components re-rendres. I tried to call $nuxt.refresh() but no any effect
Some code example (cut from real project ) , page "index.vue" :
<template>
<div>
<b-tabs content-class="mt-3">
<b-tab title="Test" active><shdashboard searchtype="test"> </shdashboard></b-tab>
</b-tabs>
</div>
</template>
<script>
export default {
name : "index",
async asyncData (context) {
if(process.server)
{
const host = "http://SOMEHOST";
const http = context.$http;
const data = await http.$get(url);
console.log('in server render')
/*
some logic
commit store and prepare data fot b-tab component
*/
}
},
methods : {
refresh() {
console.log("method refresh")
this.$nuxt.refresh(); // Not any effect.
}
},
mounted() {
console.log("page mounted");
setInterval(()=>{
/*
I need to reload page every 15 sec, and call asyncData() to get new values for
<b-tab> component
*/
this.refresh();
},15000);
}
</script>
What do I wrong ?

One way, if I understand your issue correctly, is to use the Vuex store for storing whatever you are fetching in fetch/asyncData. That way you don't need to worry about the internals of Nuxt.js, and what hooks are triggered when.
I.e. something like this:
export default {
computed: {
someData () {
return this.$store.state.someData
},
},
async asyncData ({ store, $axios }){
let data = await $axios.$get(endpoint)
store.commit('setSomeData', data)
},
}
Then just use {{ someData }} in your template!

Related

How to update default values of reactive() in Vue 3?

I am building an app with Nuxt 3 and using Composition API with async data. Here is the situation: I have a page that will contain articles which data is fetched from the database via useLazyFetch() and each article page is in one single file named projects/[project].vue.
Here projects/[project].vue:
<script setup lang="ts">
const route = useRoute();
const projectName = route.params.project;
const { pending, data } = useLazyFetch("/api/project", { params: { name: projectName } });
const state = reactive({
title: data.value?.project?.title ?? '',
});
</script>
<template>
<div>
<h1>{{ state.title }}</h1>
<input v-model="state.title" />
</div>
</template>
It works fine, but when I go back to my main page to chose a different article, the data remains the same. Therefore, the content of the first visited article stays whereas I would like the state to get the value of the new visited article. Indeed, it is conserved despite the navigation between pages.
NOTE: data.value.project can be null.
I tried to enforce it with refresh:
<script setup lang="ts">
const route = useRouter();
const projectName = route.params.project;
const { pending, data, refresh } = useLazyFetch("/api/project", { params: { name: projectName } });
const state = reactive({
title: data.value?.project?.title ?? '',
});
watchEffect(async () => {
if (!pending.value && data.value.project != null) {
if (projectName !== data.value.project.id) { // it means the current data doesn't belong to the project selected in the URL
await refresh(); // this doesn't refresh the state... I don't even know if it's useful
}
}
});
</script>
<template>
<div>
<h1>{{ state.title }}</h1>
<input v-model="state.title" />
</div>
</template>
This method doesn't change anything.
NOTE: using ref instead of reactive doesn't change anything either.
NOTE: using useFetch doesn't work either.
NOTE: using useLazyAsyncData doesn't work either.
To sum up, how do I update the default values of reactive() using Composition API.
watchEffect runs immediately on component creation and any time it's dependencies change. I'm not familiar with refresh() but why not move
{ pending, data} = useLazyFetch(...) and update state directly in the watchEffect? similar to this example in the Vue docs
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(id.value)
// `cancel` will be called if `id` changes
// so that previous pending request will be cancelled
// if not yet completed
onCleanup(cancel)
data.value = await response
})

ReactJS - Updating a component who requires an API call without refreshing the page

I have my main page component, which I can get my products informations from my API.
Everything is working, but now I have another component called updateProducts with another feature where I can update my products price. And right after this update, I need to fetch my products informations again without refreshing the page.
I came up with this and everything is working the way I want, but I don't know if this is the proper way of doing it.
Code below:
Main component that fetch the data from my API:
export default function MainComponent() {
const [products, setProducts] = useState([])
// I created this new state and I'm passing the setState as a props to my updatePrice component
const [productsCallback, setProductsCallback] = useState("")
// Fetching the data from my API
useEffect(() => {
const getProducts = async () => {
try {
const { data: { rows } } = await api.get(`/community/${id}/product`)
setProducts(rows)
} catch (error) {
toast.error('Failed to fetch data from the server.')
}
}
getProducts()
// everytime productsCallback changes, I'll get the data from my API
}, [productsCallback])
}
// Passing the setState as a prop
<UpdatePrice
setProductsCallback={() => setProductsCallback(Math.random())}
/>
UpdatePrice component:
export default function UpdatePrice({ setProductsCallback }) {
function updateProductPrice() {
updateProductAction(id, price)
// Right after the product price is updated, the productsCallback state is
// also changed, and my API is going to fetch the data again, with is exactly what I need.
setProductsCallback()
}
}
And what if I need to do exactly like this, but my updatePrice component was like 2 or 3 components away from my mainComponent? Instead of passing the setState props from component to component, what is the best way of doing this?

How to set watcher for router-view i vue

I'm begginer in vue and i can't resolve my problem with VueRouter.
I got main app component like
<template>
<div>
<Header />
<router-view />
<Footer />
</div>
</template>
One of my Router components has an function to get data from database.
import axios from 'axios';
export default {
name: 'ComponentName',
data() {
return {
dataFromDatabase: []
}
},
methods: {
getData: function() {
// Axios get function to get data from database and add it to this.dataFromDatabase array
}
},
created() {
this.getData();
}
}
Given data are based on url params and it should be changeable when clicking on link that are in header or in other places in a whole app. The problem is that it cannot change if the component won't reload. I know that the problem is that function is called after component is created and is not called again.
So my question is:
Is there any way to watch for url params changes (adding this.$route.params.param to watch() function is not working). Maybe there is a better way to set up my routes or other way to call a function except of created() function or maybe component would reload everytime the link change. As i said links to this can be everywhere even in components that are not setted up in Router
You probably just need watch which by the way is not a function but an object with methods inside
watch: {
'$route'() {
// do something
}
}
you can use a smart watcher that will be watching since the component was created:
watch: {
'$route': {
immediate: true,
handler(newValue, oldValue) {
// ...
}
}
}

How to wait on Vuex state initialization from a view component?

I'm building a Movie website to practice on VueJS. During app initialization, I get a list of movie genres from 3rd-party API. Since this list is needed in several components of the app, I manage and store it via Vuex, like so:
main.js:
new Vue({
router,
store,
vuetify,
render: h => h(App),
created () {
this.$store.dispatch('getGenreList')
}
}).$mount('#app')
Vuex's index.js:
export default new Vuex.Store({
state: {
genres: []
},
mutations: {
setGenreList (state, payload) {
state.genres = payload
}
},
actions: {
async getGenreList ({ commit }) {
try {
const response = await api.getGenreList() // axios call defined in api.js
commit('setGenreList', response)
} catch (error) {
console.log(error)
}
}
}
})
Now, in my Home view, I want to retrieve a list of movies for each genres, something like this:
Home.vue:
<script>
import { mapState } from 'vuex'
import api from '../api/api'
export default {
name: 'home',
data () {
return {
movies: null
}
},
computed: {
...mapState({
sections: state => state.genres
})
},
async mounted () {
const moviesArray = await Promise.all(
this.sections.map(section => {
return api.getMoviesByGenre(section.id)
})
)
this.movies = moviesArray
}
}
</script>
The issue here is that, on initial load, sections===[] since genres list hasn't been loaded yet. If I navigate to another view and come back, sections holds an array of genres objects as expected.
Question: How can I properly wait on sections to be loaded with genres? (since the getGenreList action isn't called from that component, I can't use this method)
I was thinking in implementing the movie list retrieval in a Watcher on sections instead of in mounted() but not sure if it's the right approach.
Yep, it is right approach, that's what watchers are for.
But if you only can... try to do actions like this one inside one component family. (parent passing props to children, controlling it);
You can read this article, about vuex - https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/.
It will maybe clarify this idea. Just simply don't store in vuex everything, cause sometimes it' does not make sense
https://v2.vuejs.org/v2/api/#watch - for this one preferably you should use immedaite flag on watcher and delete mounted. Watcher with immedaite flag is kinda, watcher + created at once

Fetch global data for the whole app in Next.js on initial page load

In my Next.js application I have search filters.
Filters consist of checkboxes, and to render these checkboxes I need to fetch (GET) all possible options from the API.
Those filters are available on many pages, so regardless the page where user lands I need to fetch the data for filters immediately and put it in the local storage to avoid further excessive API calls. Putting API call in each page is not an option.
I see the one option is to put the API call in getInitialProps in _app.js, but then according to Next.js docs automatic static optimization will not work and every page in my app will be server-side rendered.
So what is the proper way to fetch such global data in Next.js?
--------UPDATE
So at this moment I've used the next solution: in _app.js I put useEffect React Hook and once the Frontend is ready I am checking whether my data for whole application is in locale storage. If it's not then fetch data from server and put in local storage for further use.
// _app.js
const AppWrapper = ({ children }) => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch({ type: FRONTEND_LOADED });
loadInitialData(dispatch);
}, [false]);
return <>{children}</>;
};
class MyApp extends App {
render() {
const { Component, router, pageProps } = this.props;
return (
<>
<AppProvider>
<AppWrapper>
<MainLayout pathname={router.pathname}>
<Component {...pageProps} />
</MainLayout>
</AppWrapper>
</AppProvider>
</>
);
}
}
// loadInitialData.js
import {
SET_DIETS_LIST,
UPDATE_FILTERS_FROM_CACHE,
} from "Store/types";
import fetch from "isomorphic-unfetch";
export default dispatch => {
const ls = JSON.parse(localStorage.getItem("filters"));
if (ls) {
const localStorageState = {
diet: {
list: ls.diet.list || [],
selected: ls.diet.selected || [],
},
...
};
dispatch({
type: UPDATE_FILTERS_FROM_CACHE,
payload: { filters: localStorageState },
});
}
if (!ls || !ls.diet.list.length) {
fetch(`${process.env.API_URL}/diets`)
.then(r => r.json())
.then(data => {
dispatch({ type: SET_DIETS_LIST, payload: { data[0] } });
});
}
...
};
It seems this filter is located on headermenu or sidebar menu?
If that is the case, I would suggest (an option other than _app.js) putting the API caller inside header/ sidebar component, and call the header/sidebar component on layout/ pages component.
Therefore, you will get the same behavior as what you've described (not invoking SSR on every pages and static optimization is still working because the concept is similar with the _app.js (just put it inside a structure).

Categories