I have external .js file created exactly for fetching data from backend based on website locale. Here is the code:
import { ref } from "vue";
export function fetchData(section, key) {
// GET request using fetch with error handling and headers
const headers = {
method: "GET",
headers: { "Content-Type": "application/json" },
};
const fetchedData = ref(null);
fetch(
"http://localhost:4000/api/" + section + "/?api-key=" + key,
headers
)
.then(async (response) => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
fetchedData.value = data;
})
.catch((error) => {
console.error("There was an error!", error);
return error;
});
return fetchedData;
}
And this is code from .vue file where I calling fetchData function:
<script setup>
import { fetchData } from "../../utils/universal-fetch";
import { ref, watch } from "vue";
import { useStore } from "../../stores/language.js";
import { useI18n } from "vue-i18n";
import AOS from "aos";
const store = useStore();
const { locale } = useI18n({ useScope: "global" });
const fetchedData = ref(fetchData("homeFirstSection", store.getLanguage));
AOS.init();
watch(
() => locale.value,
() => {
fetchedData.value = fetchData("homeFirstSection", store.getLanguage);
}
);
</script>
When page is created/refreshed, fetchData function fetch data from backend correctly. The problem which I'm trying to solve is that, when I change a locale, watcher automatically detects that, locale was changed and variable fetchedData should be updated based on choosen locale.
Problem
Thanks!
I found a problem. Here is code:
export function async fetchData(section, key) { // Added async
// GET request using fetch with error handling and headers
const headers = {
method: "GET",
headers: { "Content-Type": "application/json" },
};
let fetchedData = null;
await fetch( // Added await
"http://localhost:4000/api/" + section + "/?api-key=" + key,
headers
)
.then(async (response) => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
fetchedData = data;
})
.catch((error) => {
console.error("There was an error!", error);
return error;
});
return fetchedData;
}
In my external .js file I was missing one more async await.
<script setup>
import { fetchData } from "../../utils/universal-fetch";
import { ref, watch } from "vue";
import { useStore } from "../../stores/language.js";
import { useI18n } from "vue-i18n";
import AOS from "aos";
const store = useStore();
const { locale } = useI18n({ useScope: "global" });
const fetchedData = ref(null);
fetchData("agreementsFirstSection", store.getLanguage).then(
(data) => (fetchedData.value = data)
); // Added .then
AOS.init();
watch(
() => locale.value,
() => {
fetchData("agreementsFirstSection", store.getLanguage).then(
(data) => (fetchedData.value = data)
); // Added .then instead of directly assign
}
);
</script>
And in .vue file I was missing .then insted of directly assigning value to variable. I added comments to code to compare changes before and after.
Problem solved
Related
I am stuck and looking for someone to help me. I've been trying to Authorize my fetch request but it always return an error. Is there someone who could explain in what way authorize header?
import { ref } from "vue";
const getAllData = () => {
const data = ref();
const entry = "user:pass";
const fetchAllData = async function () {
const res = await fetch("http://127.0.0.1:8000/articles/", {
method: "GET",
headers: {
// How to authorize?
Authorization: Bearer ${entry},
},
});
data.value = await res.json();
console.log(data.value);
};
return { data, fetchAllData };
};
export default getAllData;
I am trying to test an axios request, and I need to use an auth token in order to access the endpoint, however my test fails because I am getting "Bearer null" and inputting this into my headers.Authorization. Here is my actual code below
File I'm testing:
this.$axios.get(url, { headers: { Authorization: `Bearer ${localStorage.getItem("access-token")}` } })
.then((response) => {
this.loading = true;
// Get latest barcode created and default it to our "from" input
this.barcodeFrom = response.data.data[response.data.data.length - 1]['i_end_uid'] + 1;
this.barcodeTo = this.barcodeFrom + 1;
this.barcodeRanges = response.data.data;
// Here we add to the data array to make printed barcodes more obvious for the user
this.barcodeRanges.map(item => item['range'] = `${item['i_start_uid']} - ${item['i_end_uid']}`);
// Make newest barcodes appear at the top
this.barcodeRanges.sort((a, b) => new Date(b['created_at']) - new Date(a['created_at']));
})
.catch((error) => {
console.log('Barcode retrieval error:', error);
this.barcodeFrom === 0 ? null : this.snackbarError = true;
})
.finally(() => {
// Edge case when there's no barcode records
this.barcodeFrom === 0 ? this.barcodeTo = 1 : null;
this.loading = false
});
console.log('bcr', this.barcodeRanges);
Test file:
import Vuetify from "vuetify";
import Vuex from "vuex";
import { createLocalVue, shallowMount } from "#vue/test-utils";
import VueMobileDetection from "vue-mobile-detection";
import axios from 'axios';
import index from "#/pages/barcode_logs/index";
describe('/pages/barcode_logs/index.vue', () => {
// Initialize our 3rd party stuff
const localVue = createLocalVue();
localVue.use(Vuetify);
localVue.use(Vuex);
localVue.use(axios);
localVue.use(VueMobileDetection);
// Initialize store
let store;
// Create store
store = new Vuex.Store({
modules: {
core: {
state: {
labgroup:{
current: {
id: 1
}
}
}
}
}
});
// Set-up wrapper options
const wrapperOptions = {
localVue,
store,
mocks: {
$axios: {
get: jest.fn(() => Promise.resolve({ data: {} }))
}
}
};
// Prep spies for our component methods we want to validate
const spycreateBarcodes = jest.spyOn(index.methods, 'createBarcodes');
const createdHook = jest.spyOn(index, 'created');
// Mount the component we're testing
const wrapper = shallowMount(index, wrapperOptions);
test('if barcode logs were retrieved', () => {
expect(createdHook).toHaveBeenCalled();
expect(wrapper.vm.barcodeRanges).toHaveLength(11);
});
});
How do I mock or get the actual auth token in to work in my test?
const setItem = jest.spyOn(Storage.prototype, 'setItem')
const getItem = jest.spyOn(Storage.prototype, 'getItem')
expect(setItem).toHaveBeenCalled()
expect(getItem).toHaveBeenCalled()
You can try to mock localStorage before creating instance of a wrapper like this:
global.localStorage = {
state: {
'access-token': 'superHashedString'
},
setItem (key, item) {
this.state[key] = item
},
getItem (key) {
return this.state[key]
}
}
You can also spy on localStorage functions to check what arguments they were called with:
jest.spyOn(global.localStorage, 'setItem')
jest.spyOn(global.localStorage, 'getItem')
OR
You can delete localVue.use(axios) to let your $axios mock work correctly.
This
mocks: {
$axios: {
get: jest.fn(() => Promise.resolve({ data: {} }))
}
}
is not working because of that
localVue.use(axios)
I have multiple API calls with fairly lengthy, yet similar, response/error handling for each call.
What is the best non-repetitive ways to make multiple independent api calls that update state using fetch?
Copying and pasting 40+ instances of fetch doesn't seem right.
I want to avoid doing this ....
fetch(url,options)
.then((response) => {
// ...
return response.json
})
.then((data) => {
setState(data)
//...
})
.catch((err) => {
//Error logic here
})
Here's what I've done so far:
I made (found and modified) a useFetch hook...
useFetch.ts
//Only calls fetch() when .load() is called.
const useFetch = (path : string, HttpMethod : string, dependencies : any = [] , body : {} | undefined = undefined) => {
const history = useHistory()
const [response, setResponse] = useState<any>({});
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [controller, setController] = useState(2)
const [isReady, setIsReady] = useState<any>(false)
const load = ():void => {
setError("")
//This prevents useEffect from triggering on declaration.
if (isReady) {
//Math.random() is just to get useEffect to trigger.
setController(Math.random())
}
}
const token = localStorage.getItem("token");
let requestOptions:any = {
method: HttpMethod,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "* always",
Authorization: "Token " + token,
},
};
if (body !== undefined) {
requestOptions["body"] = {
body: JSON.stringify(body)
}
}
const URI = BASE_URI + path
useEffect(() => {
const fetchData = async () => {
if (controller !== 2) {
setIsLoading(true);
try {
const res = await fetch(URI, requestOptions);
const json = await res.json();
if (json?.action == "ENFORCE_BILLING" ) {
history.push(BILLING_CREDENTIALS_PATH, { enforceBillingPopUp: true });
}
if (json?.action == "ENFORCE_SMS_CONFIRMATION") {
// Should we log user out, as well?
history.push(CONFIRMATION_CODE_PATH)
}
if (res.ok) {
setResponse(json);
setIsLoading(false)
} else {
setError(json)
setIsLoading(false)
}
} catch (err) {
setError(err);
// Error logic here...
}
}
}
};
fetchData()
setIsReady(true)
}, [controller, ...dependencies]);
return { response, setResponse ,error, isLoading, load, isReady };
};
Component.tsx
//Inside react functional component...
// Prepares to fetch data from back-end
const data1 = useFetch(PATH1, "GET");
const data2 = useFetch(PATH2, "GET");
const data3 = useFetch(PATH3, "GET");
useEffect(() => {
// Initial on load data fetch
// .load() fetches data
data1.load();
data2.load();
data3.load();
}, [activeReservations.isReady]);
// Sort data depending on sort selection
...
Is useFetch considered bad practice? What are the advantages of using Redux, instead?
Any help would be greatly appreciated. Thanks.
I have the following react js code page:
import React, { useState } from "react";
import { Auth, API } from "aws-amplify";
function dailyFiles(props) {
const [apiError502, setApiError502] = useState(false);
// Pull out into a generic reusable function
const getData = async () => {
try {
let apiName = "Dev";
let path = "/test";
let myInit = {
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`
}
};
var result = await API.get(apiName, path, myInit);
} catch (e) {
if (e.message === "Request failed with status code 502") {
toggleApiError502(true);
} else {
alert(JSON.stringify(e));
props.onLogout();
}
}
return result;
};
const toggleApiError502 = (show = false) => {
setApiError502(show);
};
var files = {
Files: [
{
Day: "Monday",
file: "10-02-2020"
},
{
Day: "Friday",
file: "14-02-2020"
}
]
};
return (
<div className="animated fadeIn">
<div>
{files.Files.map(block => block.Day + ": " + block.file + " ")}
</div>
</div>
);
}
export default dailyFiles;
When I call from my div the static Var files variable:
var files = {Files: [{Day: "Monday",file: "10-02-2020"},{Day: "Friday",file: "14-02-2020"}]};
<div>
{files.Files.map(block => block.Day + ": " + block.file + " ")}
</div>
I got the expected result, but how can I get the same result calling my function getData()?
const getData = async () => {
getData function call an API which return the same content result as var files has?
I've tried to call the function with this.getdata() within the div but not successful result.
Use useEffect to get the data after the component has mounted.
function dailyFiles(props) {
const [apiError502, setApiError502] = useState(false);
const [files, setFiles] = useState([]);
useEffect(() => {
// Pull out into a generic reusable function
const getData = async () => {
try {
let apiName = "Dev";
let path = "/test";
let myInit = {
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`
}
};
var result = await API.get(apiName, path, myInit);
setFiles(result); // set your files here
} catch (e) {
if (e.message === "Request failed with status code 502") {
setApiError502(true);
} else {
alert(JSON.stringify(e));
props.onLogout();
}
}
return result;
};
// call getData
getData();
}, []);
return (
<div className="animated fadeIn">
<div>
{Array.isArray(files.Files) && files.Files.map(block => block.Day + ": " + block.file + " ")}
</div>
</div>
);
}
export default dailyFiles;
You're using a functional component so if you want to call the
getData function you should just use getData().
Anyway, since getData() is asynchronous you should make few
changes regarding how you use that inside the render function. For example, you can initially take a variable that has an empty array and once you get the data from the backend inside getData you can reassign that variable with the response data.
Actually you might want to call getData when your component loads for the first time so you can use Hooks for that.
There are several other ways it all comes down to your preference.
After using useMutation(), cache gets updated but useQuery() in Home component doesn't.
I tried with a different fetchPolicy. I can see new posts in Apollo dev tool but the page needs to be updated to see the changes
import { FETCH_POSTS_QUERY } from '../utils/graphql';
function Home() {
const { user } = useContext(AuthContext);
const {
loading,
data
} = useQuery(FETCH_POSTS_QUERY, {
fetchPolicy: 'cache-and-network',
});
...
import { useMutation } from '#apollo/react-hooks';
import { useForm } from '../utils/hooks';
import { FETCH_POSTS_QUERY } from '../utils/graphql';
function PostForm() {
const { values, onChange, onSubmit } = useForm(createPostCallback, {
body: ''
});
const [createPost, { error }] = useMutation(CREATE_POST_MUTATION, {
variables: values,
update(proxy, result) {
const data = proxy.readQuery({
query: FETCH_POSTS_QUERY
});
data.getPosts.push(result.data.createPost);
proxy.writeQuery({ query: FETCH_POSTS_QUERY, data });
values.body = '';
}
});
function createPostCallback() {
createPost();
}
...
...
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { ApolloProvider } from '#apollo/react-hooks';
import { setContext } from 'apollo-link-context';
const httpLink = createHttpLink({
uri: 'http://localhost:5000'
});
const authLink = setContext(() => {
const token = localStorage.getItem('jwttoken');
return {
headers: {
Authorization: token ? `Bearer ${token}` : ''
}
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
...
I expect seeing the new post without updating the page.
This code snippet solve the problem with frontend refresh when you write the cache with PostForm:
//in Home.js use this:
const [posts, setPosts] = useState([]);
const { user } = useContext(AuthContext);
const { loading, data } = useQuery(FETCH_POSTS_QUERY);
useEffect(() => {
if (data) {
setPosts(data.getPosts);
}
}, [data]);
and in PostForm.js use this:
const [createPost, { error }] = useMutation(CREATE_POST_MUTATION, {
variables: values,
update(proxy, result) {
const data = proxy.readQuery({
query: FETCH_POSTS_QUERY
});
const new_post = result.data.createPost;
proxy.writeQuery({
query: FETCH_POSTS_QUERY,
data: { getPosts: [new_post, ...data.getPosts] }
});
values.body = '';
}
});
This solution was not my idea, but one code hero help us :)
Reference Link :https://gist.github.com/josuecatalan/5b8bf73e69d55683ccf237d5d02b5cef
Original Content:
https://www.youtube.com/watch?v=F_SdB42DxdQ&list=PLMhAeHCz8S3_pgb-j51QnCEhXNj5oyl8n&index=6
You are modifying the cached data with push here:
data.getPosts.push(result.data.createPost);
and then returning it as is
proxy.writeQuery({ query: FETCH_POSTS_QUERY, data });
as this is javascript and objects are passed by reference the data you returned is the same object you received, so apollo doesn't realise it has changed. Try the following:
proxy.writeQuery({ query: FETCH_POSTS_QUERY, data: { ...data } });
In my case backend team just rewrote uploaded image file and leaved the old name. So an application UI didn't want to rerender. And I wasted few hours to debug it.
So don't forget to check the data as well.