Using fetch data as array in Vue3 - vuejs3

I'm using a composable provided by Vuejs.org that looks like this
// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
function doFetch() {
// reset state before fetching..
data.value = null
error.value = null
// unref() unwraps potential refs
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
if (isRef(url)) {
// setup reactive re-fetch if input URL is a ref
watchEffect(doFetch)
} else {
// otherwise, just fetch once
// and avoid the overhead of a watcher
doFetch()
}
return { data, error }
}
And within the script tag I'm using
let loopableValues = ref([])
const { data, error } = useFetch(
'https://jsonplaceholder.typicode.com/todos/',
)
loopableValues.value = data
return { loopableValues }
My issue is that the variable "data" is not a pure array that I can loop using something like this in the template:
<div v-for="value in loopableValues" :key="value.id"> {{ value.id}}</div>
Cause when I run this I get "Uncaught (in promise) TypeError: value is undefined". So I think I need to handle the promise somehow in order to loop out the data as above, but I'm stuck and don't know how to go forward. Do I need to use a function on the data coming back from the fetch composable or is it something else I'm misssing?

Related

Unable to use array's method "find" in Vue 3

I am trying to get current location of a user and then push it into array. Before I do so, I check whether a city with the same name is already there. In that case, I won't push it into the array. However, when I am trying to check it, it says: Uncaught (in promise) TypeError: Cannot read properties of null (reading 'find').
const found = ref(false);
const weatherResponse = ref([]);
function getLocation() {
console.log("SETTING LOCATION");
navigator.geolocation.getCurrentPosition((position) => {
console.log(`Lat: ${position.coords.latitude}, Lon: ${position.coords.longitude}`);
if (position.coords.latitude && position.coords.longitude) {
axios.get(`https://api.weatherapi.com/v1/current.json?key=${API_KEY}&q=${Math.round(position.coords.latitude)},${Math.round(position.coords.longitude)}&aqi=no`)
.then((response) => {
found.value = weatherResponse.value.find((item) => item.location.name == response.data.location.name);
if (response.data?.error?.code != 1006 && !found.value) {
weatherResponse.value.push(response.data);
this.$store.commit("addToList", response.data);
console.log(weatherResponse.value);
}
})
}
},
(error) => {
console.log(error.message);
}
)
}
I've already tried using fetch, axios to grab the API, but the "find()" method is still not working. Regarding "found" variable, I tried using it in ref as well as declaring it as "let found".
After trying and testing, I've finally managed to get everything to work. My issue was in (weirdly) main.js. Because it was set out like this: createApp(App).use(cors, store).mount('#app') it, I guess, caused VueX.store not to load in properly because mounted hook was called and it was throwing all sorts of mistakes. Putting it like const app = createApp(App); app.use(store); app.use(cors); app.mount("#app"); actually made it work.

Nuxt3: How to handle null data when using useLazyFetch and server routes?

I am using the useLazyFetch() composable to fetch server route data from Firebase. I have a problem where when navigating to the page where the data is being fetched, I get an error:
runtime-core.esm-bundler.js:38
[Vue warn]: Unhandled error during execution of mounted hook
at <Index onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {__v_skip: true} > key="/post/sed-ut-ultrices-urna-ac-molestie-nulla_jqF8" >
...SNIP...
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'data')
at index.vue:162:1
at callWithErrorHandling (runtime-core.esm-bundler.js:155:22)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:164:21)
at hook.__weh.hook.__weh (runtime-core.esm-bundler.js:2687:29)
at flushPostFlushCbs (runtime-core.esm-bundler.js:358:32)
at flushJobs (runtime-core.esm-bundler.js:403:9)
Here is my script setup file:
watch(post, (newVal) => {
if (newVal) {
// <----- What to write here???
}
})
onMounted(() => {
if (userStore.userProfile) {
// watch the bookmark
const { firestore } = useFirebase()
const docRef = doc(
firestore,
`users/${userStore.userProfile.uid}/bookmarks`,
post.value.data.id // <--- this seems to be the problem as data is `null`
)
unsubscribe.value = onSnapshot(docRef, {
next: (doc) => {
if (doc.exists()) {
watchedBookmark.value = [doc.data().id]
loading.value = false
} else {
watchedBookmark.value = []
loading.value = false
}
},
error: (err) => {
console.error('watchedBookmark listener error', err)
}
})
}
loading.value = false
})
According to the docs, it looks like I may need to:
the async function does not block navigation. That means you will need
to handle the situation where the data is null
` https://v3.nuxtjs.org/guide/features/data-fetching#uselazyfetch
OK, so how exactly would I do that when doing the data fetch like so?
const { pending, data: post } = useLazyFetch(`/api/posts/${route.params.id}`, {
initialCache: false
})

How to test the return value of a promise in a Cucumber test?

I'm trying to test the return value of a get request to a couchdb node.
I have a feature defined with the following Given clause:
Given A get request is made to the DB
which is implemented with the following step function:
var Profile = require('../UserProfile.js')
Given('A get request is made to the DB', function () {
var text = Profile.getDB('localhost:5984').then(data => {
console.log(data)
})
});
The above step references this model:
var axios = require('axios')
module.exports = {
getDB: function(url){
return axios.get(url).then(response => {
return response.data
})
}
};
I can't seem to log the result of the GET request when I perform it in the model and reference it in the step definition. When I do the GET request in the step definition, it works - but this isn't useful to me, I want to test the model. How do I get the resulting value?
Cucumber 2.0 supports Promises as returns, try with:
const Profile = require('../UserProfile')
const { defineSupportCode } = require('cucumber')
defineSupportCode(({ defineStep }) => {
defineStep('A get request is made to the DB', function () {
return Profile.getDB('http://localhost:5984').then(data => {
console.log(data)
})
})
})

React-Reduc's connect does not pass anything back to my component

So I'm a little stumped with React-Redux's connect, in that it seems to not pass anything back to my component. I've been trying to debug this all morning now, and I've looked at a few tutorials but I can't see what I've done wrong. Here's a snippet of the relevant code below. Am I misunderstanding connect()?
const TodoList = (props) => {
console.log(props); // Returns only an object with one property
// dispatch which holds a function. Where
// are my todos, filter, toggleTodo & removeTodo?
return (
<li> placeholder </li>
)
}
const RenderTodoGenerated = connect(
todoStateToProps,
todoDispatchToProps
)(TodoList);
const todoStateToProps = (state) => {
return {
todos: state.todos,
filter: state.filter
}
}
const todoDispatchToProps = (dispatch) => {
return {
toggleTodo: (toggleId) => {
dispatch(toggleTodo(toggleId));
},
removeTodo: (removeId) => {
dispatch(removeTodo(removeId));
}
}
}
In ES6, variables declared with const do not exist until the line they're declared on. Your call to connect occurs before todoStateToProps and todoDispatchToProps actually exist, so at that point they're undefined. You need to move the call to connect to the end of that chunk of code.
Also, as an FYI, connect supports an "object shorthand" for handling the mapDispatch argument. Instead of writing an actual function as you have in that example, you can simply pass an object full of action creators as the second argument, and they will be wrapped up with dispatch. So, all you need is:
const actionCreators = {toggleTodo, removeTodo};
const RenderTodoGenerated = connect(todoStateToProps, actionCreators)(TodoList);

React-redux project - chained dependent async calls not working with redux-promise middleware?

I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}

Resources