Why does it does not return any data when using Async with Axios in Nuxt? - async-await

I dont get any of the data in my list. When I use the fetch it works (please see the comment code in the script tag), but not when I use the axios.
Here is the code:
<template>
<div>
<ul>
<li v-for="(mountain, index) in mountains" :key="index">
{{ mountain.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
mountains: [],
};
},
/*async fetch() {
this.mountains = await fetch(
"https://api.nuxtjs.dev/mountains"
).then((res) => res.json());
}, */
async asyncData() {
const mountains = await axios.get(`https://api.nuxtjs.dev/mountains`);
return { mountains };
},
};
</script>
JSON from https://api.nuxtjs.dev/mountains

Try not to destructure the response
async asyncData() {
const mountains = await axios.get(`https://api.nuxtjs.dev/mountains`);
return { mountains };
},

In the help documents of Nuxt Axios I found out you need to add $axios as a parameter and add a $ before axios and get. See code below:
async asyncData({ $axios }) {
const mountains = await $axios.$get(`https://api.nuxtjs.dev/mountains`);
return { mountains };
},
Now it works perfect!

Related

Nuxt3 useFetch dynamic request not working

I want to send multiple requests with different url parameters like a pagination feature. I found that useAsyncData works well but useFetch doesn't work. Is this a bug in nuxt codebase or not?
the codesandbox is here: https://codesandbox.io/p/sandbox/little-fog-y5gevk?file=%2Fapp.vue&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A33%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A1%7D%5D
<script setup>
const count = ref(1);
// doesn't work
// const { data, refresh } = await useFetch(`/api/hello`, {
// query: { count: count.value },
// });
// it works
const { data, refresh } = await useAsyncData("hello", () =>
$fetch("/api/hello", {
query: { count: count.value },
})
);
function add() {
count.value++;
refresh();
}
</script>
<template>
<NuxtExampleLayout example="composables/use-fetch">
<div>
Fetch result:
<pre class="text-left"><code>{{ data }}</code></pre>
<NButton #click="count++"> + </NButton>
<NButton #click="add">add</NButton>
</div>
</NuxtExampleLayout>
</template>

why is an array fetched from backend not in the same order in redux store (react app)?

In my React app, i am fetching an array of posts from a backend api (nodejs/SQL DB).
I am using redux for the frontend, so i thought it would be a good idea to sort the posts on the backend and send them to the frontend (sorted by id, from latest to oldest).
Then, the array of posts gets stored in my redux store.
It's working fine, but i am confused because when i check the store, the posts are not ordered anymore, or rather: the same 4 random posts always get "pushed" to the top and then the rest is ordered as i wanted.
So when i refresh the page i can see these older random posts in the UI at the top of the thread/feed of posts and when component is fully mounted it renders posts in the correct order. Not good.
I wanted to avoid sorting the array of posts on the frontend for performance concerns, am i wrong?
Redux initial state:
const initialState = {
posts: [],
userPosts: [],
currentPost: {
title: "",
text: "",
imgUrl: "",
},
scrapedPost: {},
comments: [],
replies: [],
likes: [],
error: "",
lastPostAdded: null,
lastReplyAdded: null,
lastDeleted: null,
sessionExpired: false,
users: [],
};
Redux root reducer:
import { combineReducers } from "redux";
import { postsReducer } from "./posts.reducer.js";
import { userReducer } from "./user.reducer.js";
export const rootReducer = combineReducers({
user: userReducer,
posts: postsReducer,
});
Redux store config:
import { applyMiddleware, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistReducer, persistStore } from "redux-persist";
import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
import storage from "redux-persist/lib/storage";
import thunk from "redux-thunk";
import { rootReducer } from "./reducers/root.reducer";
const composeEnhancer = composeWithDevTools({ trace: true, traceLimit: 25 });
const persistConfig = {
key: "root",
storage,
stateReconciler: autoMergeLevel2,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, composeEnhancer(applyMiddleware(thunk)));
const persistor = persistStore(store);
export { store, persistor };
getPost action creator (using thunk middleware for async task):
export const getPosts = () => async (dispatch) => {
const accessToken = localStorage.getItem("jwt");
const request = {
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${accessToken}`,
},
method: "get",
};
try {
const response = await fetch(API_POST, request);
const data = await response.json();
const { posts, likes, sessionExpired } = data;
if (sessionExpired) {
dispatch({ type: SESSION_EXPIRED, payload: sessionExpired });
return;
}
dispatch({ type: GET_POSTS, payload: { posts, likes } });
} catch (error) {
dispatch({ type: SET_ERROR_POST, payload: error.message });
}
}
the posts reducer:
export const postsReducer = (state = initialState, action) => {
switch (action.type) {
case GET_POSTS: {
const { posts, likes } = action.payload;
return { ...state, posts, likes };
}
case GET_LIKES: {
const { likes } = action.payload;
return { ...state, likes };
// all other actions...//
}
relevant part of the UI code (feed component):
const Feed = () => {
const [newUser, setNewUser] = useState(false);
const user = useSelector((state) => state.user);
const { isAuthenticated, isNewUser } = useSelector((state) => state.user);
const posts = useSelector((state) => state.posts.posts);
const dispatch = useDispatch();
const userLanguage = useLanguage();
useEffect(() => {
window.scrollTo(0, 0);
setNewUser(isNewUser);
return function cleanup() {
setNewUser(null);
};
}, [isNewUser]);
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Layout>
//some jsx...//
<button className="h-6 refreshBtn outline-none hover:cursor-pointer bg-blue-500
text-white rounded-full gap-1 flex items-center justify-center pl-2 pr-3 py-1
shadow transition-all duration-300 hover:bg-black hover:shadow-none group"
onClick={() => dispatch(getPosts())}
style={{ opacity: posts && posts.length !== 0 ? 1 : 0 }}>
<RefreshIcon className="h-4 w-4 pointer-events-auto transform transition
transform duration-500 group-hover:-rotate-180" />
<span className="text-xs pointer-events-auto capitalize">
{userLanguage?.feed.refreshBtn}</span>
</button>
<div className="posts-wrapper h-full w-full relative flex flex-col items-center
justify-center gap-4 pb-6">
{posts.length === 0
? (<Skeleton element="post" number={8} />)
: (posts.map((post) => <Post key={post.postId} post={post} />)}
</div>
</Layout>
};
posts ordered by Id on the backend:
screenshot
posts in the redux store (as you can see by their postId, indexes 0 to 3 have nothing to do there)
screenshot
so my questions:
how come the array fetched is not in the same order in redux store?
why does the UI flash the "wrong" order for a sec, then the correct order? how does it know the correct order if those 4 posts are still at the top in the store?
i'm confused here, any hint or help is appreciated! thanks
I finally found the solution months ago but forgot to come back here to give the solution to the issue i had.
Turns out the order of the posts fetched from backend wasn't modified or messed up with by Redux at all but by me (of course!) from another component called PopularPosts.
Consider the code below:
const PopularPosts = () => {
const { posts } = useSelector(state => state.posts);
const [top3, setTop3] = useState<IPost[]>([]);
useEffect(() => {
setTop3(posts.sort((a, b) => { my sorting logic }).splice(0, 3));
}, [posts]);
I was literally mutating the store directly in order to create my top3. Of course this was a HUGE mistake! I should have used the sort() method on a COPY of the store, not the store itself.
Here is the correct code:
const PopularPosts = () => {
const { posts } = useSelector(state => state.posts);
const [top3, setTop3] = useState<IPost[]>([]);
useEffect(() => {
const postsCopy = [...posts];
setTop3(postsCopy.sort((a, b) => { // my sorting logic }).splice(0, 3));
}, [posts]);
All is working as intended since this correction.
And lesson learnt: i'll never mutate the Redux store directly ever again ;)

vue.js how to call multiple url data in single axios

i am trying get multiple url data in single axios. i already added single url but i want to add another url.
i tired this but it giving null object error
{{ BusinessCount }}
{{ UserCount }}
import axios from "axios";
export default {
data() {
return {
businesslists: [],
Userslist: [],
};
},
async asyncData({ $axios }) {
let { datas } = await $axios.$get("/Userslist");
return {
Userslist: datas,
};
},
computed: {
UserCount() {
return Object.keys(this.Userslist).length;
},
},
async asyncData({ $axios }) {
let { data } = await $axios.$get("/Businessregisterlist");
return {
businesslists: data,
};
},
computed: {
BusinessCount() {
return Object.keys(this.businesslists).length;
},
},
};
i want to show like this
<p>{{ BusinessCount }}</p>
<p>{{ UserCount }}</p>
1st url
/Businessregisterlist
2nd url
/Userlist
my code
<template>
<p>{{ BusinessCount }}</p>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
BusinessCounts: [],
};
},
async asyncData({ $axios }) {
let { datad } = await $axios.$get("/Businessregisterlist");
return {
BusinessCounts: datad,
};
},
computed: {
BusinessCount() {
return Object.keys(this.BusinessCounts).length;
},
},
};
</script>
In your tried code you define the asyncData function 2 times. That's incorrect. But you can make 2 calls to the server in a single asyncData function.
Try:
async asyncData({ $axios }) {
let { datad } = await $axios.$get("/Businessregisterlist");
let { dataUsers } = await $axios.$get("/Userslist");
return {
Businesslist: datad,
Userslist: dataUsers
};
},
computed: {
BusinessCount() {
return Object.keys(this.Businesslist).length;
},
UserCount() {
return Object.keys(this.Userslist).length;
},
},
Make sure you correctly define the Businesslist and Userslist in the data section.

Error in mounted hook: "ReferenceError: posts is not defined in Vuejs

I'm newbie in Vuejs. I am try to learning to code vuejs for couple of hours until I get this error.I guess the problem is come from props to to Blade. Here is my code.
// Blade View
<div id="app">
<div :posts="{{ $posts }}"></div>
</div>
// Vue Template
<table striped hover :items="imageList">
<template slot="image" slot-scope="data">
<img :src="'storage/images' + data.item.image" alt="">
</template>
</table>
// Vue JS
<script>
export default {
props:['posts'],
data: function() {
return {
imageList: []
};
},
mounted() {
this.fetch_image_list();
},
methods: {
fetch_image_list() {
let items = [];
if (Array.isArray(posts.data) && posts.data.length) {
posts.data.forEach((image,key) => {
let currentImage = {
'id':post.id,
'name':post.name,
'image':post.img,
}
items.push(currentImage)
});
this.imageList = items;
}
}
}
}
</script>
You should use this when accessing your data (if you don't have another scope defined inside). And you're trying to access the properties of undefined object (post) in forEach loop.
methods: {
fetch_image_list() {
let items = [];
if (Array.isArray(this.posts.data) && this.posts.data.length) {
this.posts.data.forEach((post, key) => {
let currentImage = {
'id':post.id,
'name':post.name,
'image':post.img,
}
items.push(currentImage)
});
this.imageList = items
}
}
}

Avoid unnecessary http requests on identical images - vuejs

Situation:
In a page, there are several components that receive a list of users. After receiving the list, there's a foreach cycle that calls an aditional component to fetch the user's image. It's possible that the several components may contain the same user, which would mean repeating the exact same http request's to fetch a "repeated image". To avoid these unecessary requests, I set the information of a user has a certain base64 image in the store of vueX, so that I can validate if I already got the image.
Problem: Happens that when the first component makes the request to fetch the image and save it in the store, the remaining components have already been created and as such, the store is still empty and I can't check if I have the image.
Solution: When I create the component, I force the store to exist by using
this.images[this.user.id] = 'reserved';
However, I'm not sure if this is the right approach to this situation.
Suggestions accepted :'D
Code:
parent component
<template>
<div class="info-cards">
<div class="info-users">
<div class="info-label">{{ $t('global.users') }}</div>
<div class="info-images" v-if="users.length > 0">
<base-users-image
v-for="user in users"
:key="user.name"
:user="user"
/>
</div>
<div v-else class="message">{{ $t('global.noUsersRole') }}</div>
</div>
</div>
</template>
<script>
// import components
const baseUsersImage = () => System.import(/* webpackChunkName: 'usersImage' */ './../../users/baseUsersImage');
export default {
props: {
users: Array,
packages: Array
},
components: {
baseUsersImage: baseUsersImage
},
}
</script>
image component
<template>
<router-link to="user" class="anchor-image">
<img v-if="show" :src="image" :alt="user.name" class="image">
<div v-else class="image-default">t</div>
</router-link>
</template>
<script>
// import requests
import requests from './../../../helpers/requests.js';
// import store
import { mapGetters, mapActions } from 'vuex';
export default {
props: {
user: Object
},
data() {
return {
image: '',
show: false
}
},
created() {
if (this.user.avatar) { // check if user has avatar
if ( this.images[this.user.id] == null) { // check if it already exists in the store
this.images[this.user.id] = 'reserved'; // set as reserved in store
requests.get(this.user.avatar, { responseType: 'arraybuffer' }) // faz o pedido a API da image
.then( (response) => {
this.saveImage( { id: this.user.id, url: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}` } );
}, error => {
console.log(error);
});
}
}
},
methods: {
...mapActions({
saveImage: 'saveImage'
})
},
computed: {
...mapGetters({
images: 'images'
})
},
watch: {
images: {
immediate: true,
deep: true, // so it detects changes to properties only
handler(newVal, oldVal) {
if ( newVal[this.user.id] !=='reserved'
&& this.user.avatar
&& newVal[this.user.id] !== undefined
) {
this.image = newVal[this.user.id];
this.show = true;
}
}
}
}
}
</script>
store
const state = {
images: {}
}
const SAVE_IMAGE = (state, payload) => {
state.images = {
...state.images,
[payload.id] : payload.url
}
}
const saveImage = ({commit}, payload) => {
commit('SAVE_IMAGE', payload);
}
Here is what I would do:
First, I would move all the request logic to VueX and keep my component as simple as possible. It should be achievable by this piece of code:
export default {
props: {
user: Object
},
created () {
if (this.user.avatar) {
this.$store.dispatch('fetchImage', this.user.avatar)
}
}
}
Then, I would use this simple pattern to organize my store. First, let's take a look at how the state should look:
{
images: {
'/users/1/avatar': 'data:png:base64,....', // An image that have been loaded
'/users/2/avatar': null // An image that is supposed to be loading
}
}
As you can see, the images object uses images urls as keys and base64 data as value. If the value of the data is null, it means that the image is already loading.
Let's now see how do we write the action to handle that:
const actions = {
fetchImage ({state, commit}, url) {
if (typeof state.images[url] !== 'undefined') {
return null
}
commit('setImage', {
url,
payload: null
})
return requests.get(url, { responseType: 'arraybuffer'}).then(response => {
commit('setImage', {
url,
payload: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}`
})
})
}
}
Look at the first condition. If the image is not undefined in the store, we just don't do anything. Because if the image is not undefined, it means that it is either null (loading) or has a value and is loaded.
Just after this condition, we set the image to null to prevent other components to load the image.
And at the end we load the content of the image, and commit it to the state.
Let's take a look to the template now:
<template>
<router-link to="user" class="anchor-image">
<img v-if="$store.state.images[user.avatar]" :src="$store.state.images[user.avatar]" :alt="user.name" class="image">
<div v-else class="image-default">t</div>
</router-link>
</template>
In order to check if you should display the image, you just have to use v-if="$store.state.images[user.avatar]". The image will show up as soon as it is loaded.
$store.state.images[user.avatar] will be falsy even if the image is loading (it has the null value.
I hope this can help!
(Here is the complete store:)
const store = {
state: {
images: {}
},
mutations: {
setImage (state, image) {
Vue.set(state.images, image.url, image.payload)
}
},
actions: {
fetchImage ({state, commit}, url) {
if (state.images[url] !== undefined) {
return null
}
commit('setImage', {
url,
payload: null
})
return requests.get(url, { responseType: 'arraybuffer'}).then(response => {
commit('setImage', {
url,
payload: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}`
})
})
}
}
}

Resources