I created a composable as follows to attach a header whenever I use the useFetch function.
export const useCommon = () => {
async function useFetchToken(api: string, option: useFetchTokenTyle){
const autoOption:any = {
headers: (option.headers) ?? { Authorization: 'Bearer ' + ado_token.value }
}
...
return await useFetch(api, {...option,...autoOption})
}
return {
useFetchToken
}
}
and I use this useFetchToken composable twice in one
<script setup lang="ts">
const { useFetchToken } = useCommon()
let {data:category_lists, refresh:cateRefresh} = await useFetchToken('/api/action', {
baseURL: CATEGORY_API,
method: 'post',
body: {
action: 'DoSomeThing',
}
})
let {data:section_lists, refresh:sectionRefresh} = await useFetchToken('/api/action', {
baseURL: CATEGORY_API,
method: 'post',
body: {
action: 'DoSomeThing2',
}
})
Then, a strange result occurs.
"category_lists" affect from second useFetchToken composable.
"category_lists" is overwritten by 2nd useFetchToken composable.
When I delete 2nd useFetchToken part, "category_lists" work correctly.
When I change useFetchToken to useFetch, it will work well all.
Related
I have a nextjs project that is using apollo graphql to fetch data from the backend. I am trying to render my page using server side rendering. But I am currently using graphql apollo hooks to fetch my data from the backend, and the react hooks prevents me from calling my backend inside of the getServerSideProps.
Create and fetch single page using graphql from Wordpress with clean URLs like services/[id].js
N.B: Warning Show ( Error: Response not successful: Received status code 500)
import {
gql,
ApolloClient,
InMemoryCache
} from "#apollo/client";
export const client = new ApolloClient({
uri: 'https://.........../graphql',
cache: new InMemoryCache()
});
const serviceDetail = (serviceOutput) => {
return (
<div>
{serviceOutput.serviceTitle}
{serviceOutput.serviceContent}
</div>
)
}
export const getServerSideProps = async (context) => {
const result = await client.query({
query: gql`
query serData($id: id!) {
HomePage: pageBy(uri: "https://......./home/") {
aboutSection {
serviceSec(id: $id) {
id
serviceTitle
serviceContent
serviceImage {
sourceUrl
}
}
}
}
}
`,
variables: {
id: context.params.id
}
})
return {
props: {
serviceOutput: result.data.HomePage.aboutSection.serviceSec;
},
};
}
export default serviceDetail;
i am not an expert, but as far i have used. you cannot use Apollo together with next js fetching method(ssg,ssr,isr).
Apollo runs queries on client side, and can be used with useQuery and useLazyQuery. while next js fetching is completely different.
I will demonstrate 2 ways here.
-- Using Apollo --
const FETCH_ALL = gql`
query MyQuery($first: Int!, $after: String) {
posts(first: $first, after: $after) {
edges {
node {
title
}
}
}
}
`;
export default function LoadMoreList() {
const { data } = useQuery(FETCH_ALL, {
variables: { first: 5, after: null },
notifyOnNetworkStatusChange: true,
});
return (
<>
<div>
{postdata.map((node, index) => {
{
return (
<div key={index}>
<h1>{node?.node?.title}</h1>
</div>
);
}
})}
</div>
</>
)}
=== using fetch and getStaticProps ==
--File1 (this is a fetch function, to which you pass your queries and variables)
async function fetchAPI(query, { variables } = {}) {
const headers = { "Content-Type": "application/json" };
const res = await fetch(process.env.WP_API, {
method: "POST",
headers,
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
console.log(json.errors);
throw new Error("Failed to fetch API");
}
return json.data;
}
export default fetchAPI;
-- File2 (this is a file that contains your query)
import fetchAPI from "./fetching";
export async function homeheadposts() {
const data = await fetchAPI(
`
query homeheadposts {
posts(first: 7) {
edges {
node {
id
slug
title
featuredImage {
node {
sourceUrl
}
}
excerpt(format: RAW)
}
}
}
}
`
);
return data?.posts;
}
-- File3 (place this function , where you wanna call and use the data, )
export async function getStaticProps() {
const latestPosts = await homeheadposts();
return {
props: { latestPosts },
};
}
export default function CallingData({ latestPosts }) {
console.log(latestPosts);
return <h1>hello</h1>;
}
I am experimenting with Vue3's Composition API in a Laravel/VueJS/InertiaJS stack.
A practice that I have used a lot in Vue2 with this stack is to have 1 route that returns the Vue page component (eg. Invoices.vue) and then in the created() callback, I would trigger an axios call to an additional endpoint to fetch the actual data.
I am now trying to replicate a similar approach in Vue3 with composition API like so
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: getInvoices(),
selectedInvoices: [],
});
async function getInvoices() {
loading.value = true;
return await axios.get(props.fetch_url).then(response => {
return response.data.data;
}).finally(() => {
loading.value = false;
})
}
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
This however keeps on giving me the propise, rather than the actual data that is returned.
Changing it like so does work:
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: [],
selectedInvoices: [],
});
axios.get(props.fetch_url).then(response => {
state.invoices = response.data.data;
}).finally(() => {
loading.value = false;
})
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
I want to use function though, so I can re-use it for filtering etc.
Very curious to read how others are doing this.
I have been googling about it a bit, but cant seem to find relevant docu.
All feedback is highly welcomed.
I tried this now with async setup() and await getInvoices() and <Suspense> but it never displayed any content.
So this is how I'd do it, except I wouldn't and I'd use vuex and vuex-orm to store the invoices and fetch the state from the store.
<template>
<div>loading:{{ loading }}</div>
<div>state:{{ state }}</div>
</template>
<script>
import {defineComponent, ref, reactive} from "vue";
import axios from "axios";
export default defineComponent({
name: 'HelloWorld',
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: []
})
async function getInvoices() {
loading.value = true;
await axios.get(props.fetch_url).then(response => {
state.invoices = response.data;
}).finally(() => {
loading.value = false;
})
}
return {
getInvoices,
loading,
state,
}
},
async created() {
await this.getInvoices()
}
})
</script>
<style scoped>
</style>
This is of course similar to what you're doing in option 2.
I wanted to work with Shopify's address library. Since these work with promises I thought about implementing callbacks in order to receive the results
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict'
import AddressFormatter from '#shopify/address';
import './main.html';
const address = {
company: 'Shopify',
firstName: '恵子',
lastName: '田中',
address1: '八重洲1-5-3',
address2: '',
city: '目黒区',
province: 'JP-13',
zip: '100-8994',
country: 'JP',
phone: '',
};
Template.hello.onCreated(function () {
const addressFormatter = new AddressFormatter('ja');
const instance = this
instance.state = new ReactiveDict()
instance.state.setDefault('result', {
"formattedAddress": "",
"orderedFields": ""
});
getData(addressFormatter, function(r) {
// the next line triggers the helper, since it "observes" the changes
// to this "result" property on the reactive-dictionary
instance.state.set('result', {
formattedAddress: r.formattedAddress,
orderedFields: r.orderedFields
});
});
})
Template.hello.helpers({
address: function() {
console.log(Template.instance().state.get("result"));
return Template.instance().state.get('result')
}
});
function getData(addressFormatter, callback) {
const fa = async () => {
const result = await addressFormatter.format(address);
console.log(result)
return result;
}
const of = async () => {
const promise = addressFormatter.getOrderedFields('CA');
promise.then(result => {
console.log(result);
return result;
});
}
let results = {
"formattedAddress": fa(),
"orderedFields": of()
}
callback(results);
}
The only thing that I receive in the template are [object Promise]. The console.logs in the getData() method actually show the accurate data but they are not displayed in teamplte. What can I do to receive the values and make my helper wait for them?
Edit: I have edited it according to #Jankapunkt answer but the objects are still empty, while the results in getData() are not.
You don't. Helpers are there to immediately return values but are triggered by reactive data sources.
If you want a helper to "run" once the data "arrived" then your should move this code into onCreated and store the value in a reactive data source:
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict'
import AddressFormatter from '#shopify/address';
import './main.html';
const address = {
company: 'Shopify',
firstName: '恵子',
lastName: '田中',
address1: '八重洲1-5-3',
address2: '',
city: '目黒区',
province: 'JP-13',
zip: '100-8994',
country: 'JP',
phone: '',
};
Template.hello.onCreated(function () {
const instance = this
instance.state = new ReactiveDict()
instance.state.setDefault('result', {
"formattedAddress": "",
"orderedFields": ""
})
const addressFormatter = new AddressFormatter('ja')
getData(addressFormatter)
.then(({ formattedAddress, orderedFields }) => {
// the next line triggers the helper, since it "observes" the changes
// to this "result" property on the reactive-dictionary
instance.state.set('result', { formattedAddress, orderedFields })
})
.catch(e => console.error(e))
return results;
})
Template.hello.helpers({
address: function() {
return Template.instance().state.get('result')
}
});
const getData = async function (addressFormatter) {
const formattedAddress = await addressFormatter.format(address)
const orderedFields = await addressFormatter.getOrderedFields('CA')
return {
formattedAddress,
orderedFields
}
}
Readings: http://blazejs.org/
Edit: added a simplified getData that should work
I want to test that when i type a value in an input(inputA), anoter input(inputB) gets updated with a value.
inputA accepts a postal code e.g: "10999", after inputB shows a location: "Berlin"
This works on the actual app, i type in inputA, and inputB gets updated.
When ome types on inputA, an action is dispatched and then inputB gets a new value from the redux state.
This is my test code, any ideas why it doesnt updates the input with placeholder of "Ort" on the test, but it does on the actual app?
import { render, withIntl, withStore, configureStore, withState } from "test-utils-react-testing-library";
import { screen, fireEvent, withHistory, withRoute, within } from "#testing-library/react";
import configureMockStore from 'redux-mock-store';
import ProfileForm from "./ProfileForm";
import PersonalDetails from "../PersonalDetails/PersonalDetails";
const STATE = {
locations: { locations: {} },
streets: { streets: {} },
password: {}
};
const mockStore = configureMockStore();
const STORE = mockStore({
streets: {
isFetching: false,
},
locations: {
locations: {
isFetching: false,
},
},
user: {
session: {
impersonated_access_token: "",
},
updateError: "error",
},
});
const props = {
id: "user1",
user: { email: "max#muster.de" },
locations: {},
onSubmit: jest.fn(),
};
beforeEach(jest.resetAllMocks);
describe("ProfileForm", () => {
describe("on personal details change", () => {
it("auto selects only location when postalcode becomes selected", () => {
const locations = { electricity: { [PLZ_1]: [LOCATION_OBJ_1] } };
const user = { postalcode: null };
render(<ProfileForm {...props} user={user} locations={locations} />, [...decorators, withStore(STORE)]);
const input = screen.getByPlaceholderText("PLZ");
fireEvent.change(input, { target: { value: "10999" } })
screen.debug(screen.getByPlaceholderText("PLZ"))
screen.debug(screen.getByPlaceholderText("Ort"))
expect(screen.getByPlaceholderText("Ort")).toHaveValue("Berlin");
});
});
I guess your input hasn't been updated yet.
Try to use waitfor:
https://testing-library.com/docs/dom-testing-library/api-async#waitfor
import { waitFor } from "#testing-library/react";
const inputNode = screen. getByPlaceholderText("Ort");
// keep in mind that you need to make your test async like this
// it("auto selects only location when postalcode becomes selected", async () => {
await waitFor(() => expect(inputNode).toHaveValue("Berlin"));
If it won't work, try to add timeout:
await waitFor(() => expect(inputNode).toHaveValue("Berlin"), { timeout: 4000 });
I've encountered a similar proplem and found that changes in the microtask queue aren't always flushed, so the changes are not applied/rendered until the test is finished running. What worked for me, was to call jest.useFakeTimers() at the beginning of your testcase, and then await act(async () => { jest.runOnlyPendingTimers() }); after the call to fireEvent.<some-event>(...)
In your case:
it("auto selects only location when postalcode becomes selected", async () => {
jest.useFakeTimers();
const locations = { electricity: { [PLZ_1]: [LOCATION_OBJ_1] } };
const user = { postalcode: null };
render(<ProfileForm {...props} user={user} locations={locations} />, [...decorators, withStore(STORE)]);
const input = screen.getByPlaceholderText("PLZ");
fireEvent.change(input, { target: { value: "10999" } })
await act(async () => {
jest.runOnlyPendingTimers();
});
screen.debug(screen.getByPlaceholderText("PLZ"))
screen.debug(screen.getByPlaceholderText("Ort"))
expect(screen.getByPlaceholderText("Ort")).toHaveValue("Berlin");
});
Tried, but get this error: Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. No idea where that comes from :(
Try to use findBy instead of getBy.
https://testing-library.com/docs/dom-testing-library/api-queries#findby
import { screen, waitFor } from "#testing-library/react";
const inputNode = await screen.findByPlaceholderText("Ort");
// or with timeout: await screen.findByPlaceholderText("Ort", { timeout: 4000 });
await waitFor(() => expect(inputNode).toHaveValue("Berlin"));
Below are my action and reducer files - In my component state I am only seeing this.props.mainData - but others subdataOneData etc., are not being loaded in to the state - till reducer i see the right actions are being dispatched and I also see the data for sub - calls - but they are not reaching my component - I have mapStatetoprops - where I am doing
New issue: as per the updated code - when i print out payload in reducer - I see maindata with the api data but SubData [{}, {}, {}] ..?
Updated code:
import { GET_DATA_AND_SUBDATA } from '../constants/types';
export function getMainData() {
return async function getMainData(dispatch) {
const { data } = await getMainDataAPI();
const subData = data.map((item) => {
const endpoint = 'build with item.name';
return Request.get(endpoint);
});
console.log('subddd' + subData); prints -> **[object Promise],[object Promise],[object Promise]**
dispatch({
type: GET_DATA_AND_SUBDATA,
payload: { data, subData }
});
};
}
async function getMainDataAPI() {
const endpoint = 'url';
return Request.get(endpoint);
}
The problem lies on the way you dispatch the actions.
You are not providing data for mainData and subdataOneData at the same time.
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
// This will cause first re-render
dispatch({ type: GET_DATA, payload: data });
Object.keys(data).map((keyName, keyIndex) => {
const endpoint = 'ENDPOINT';
Request.get(endpoint).then((response) => {
// This will cause second re-render
dispatch({
type: GET_subdata + keyIndex,
payload: response.data });
});
return keyIndex;
});
};
}
At first render your subdataOneData is not availble yet.
You are not even specifying a default value in the reducer, therefore it will be undefined.
You can change your action thunk like this
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
const subDataResponse = await Promise.all(
Object.keys(data).map( () => {
const endpoint = 'ENDPOINT';
return Request.get(endpoint)
})
)
const subData = subDataResponse.map( response => response.data )
dispatch({
type: GET_DATA_AND_SUBDATA
payload: { data, subData }
});
};
}
And change your reducer accordingly in order to set all data at once.
export default function myReducer(state = {}, action) {
switch (action.type) {
case GET_DATA_AND_SUBDATA:
return {
...state,
mainData: action.payload.data,
subdataOneData: action.payload.subData[0],
subdataTwoData: action.payload.subData[1]
};
default:
return state;
}
}
Note: it's also a good practice to set your initial state in the reducer.
const initialState = {
mainData: // SET YOUR INITIAL DATA
subdataOneData: // SET YOUR INITIAL DATA
subdataTwoData: // SET YOUR INITIAL DATA
}
export default function myReducer(initialState, action) {