How can I use SWR hook for all API methods? (CRUD) - reactjs

I am switching certain CRUD functionality that I used to provide with a token, but now I am using SWR and I don't know how to convert it.
I used this hook for GET methods but for others, I don't know what to do!
export default function useGetData(apiKey) {
const fetcher = async (...args) => await fetch(...args).then(res => res.json());
const { data, mutate, error } = useSWR(apiKey, fetcher);
const loading = !data && !error;
return {
loading,
user: data,
mutate
}
}

OK, I found the answer :
import useSWR from 'swr';
import { getTokenFromLocalStorage } from '../../services/storage';
export default function useGetData({ url, payload, options = {}}) {
const mainUrl = process.env.NEXT_PUBLIC_BASE_URL + URL;
const method = payload ? 'POST' : 'GET';
const fetcher = async () => {
const headers = {
Authorization: `Bearer ${getTokenFromLocalStorage()}`
};
const options = {
method,
headers,
...(payload && { body: payload }),
};
return await fetch(mainUrl, options).then((res) => res.json());
};
const defaultOptions = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
const { data, mutate, error, isValidating } = useSWR(url + method, fetcher, {
...defaultOptions,
...options,
});
const loading = !data && !error;
return { data, loading, error, mutate, isValidating };
}

Related

Using conditions in custom react hook which uses useQuery

I have a custom react hook which is used to get a user's data by sending a POST request. I would like my custom react hook to check if the argument passed into the hook meets certain conditions, however I'm aware that I cant use conditions with hooks. Does any one have any idea how to improve my code so that I can check if an argument is valid?
...
import { useQuery } from "#tanstack/react-query";
export const useLiquidityPositions = (userAddress: string): GraphQLResponse<LiquidityPositions> => {
const {
status: status,
isLoading: loading,
error: error,
data: response,
} = useQuery({
queryKey: ["USER_POSITIONS", userAddress],
queryFn: async () => {
if (!isAddress(userAddress)) {
return undefined
}
const res = await getLiquidityPositionsData(userAddress);
return res;
}});
const payload = response?.data;
return { status, loading, error, response, payload }
}
Two ways to validate query using react-query.
1. only validate a single request. https://react-query-v3.tanstack.com/guides/dependent-queries
import { useQuery } from "#tanstack/react-query";
export const useLiquidityPositions = (userAddress: string): GraphQLResponse<LiquidityPositions> => {
const {
status: status,
isLoading: loading,
error: error,
data: response,
} = useQuery({
queryKey: ["USER_POSITIONS", userAddress],
queryFn: ()=>getLiquidityPositionsData(userAddress),
// The query will not execute until isAddress(userAddress) is true.
enabled: isAddress(userAddress)
});
const payload = response?.data;
return { status, loading, error, response, payload }
}
2. validate all request.
https://react-query-v3.tanstack.com/guides/query-invalidation
Add validate functions to queryClient
const queryClient = new QueryClient()
queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0] === 'USER_POSITIONS' && isAddress(query.queryKey[1]) ,
})
<QueryClientProvider client={queryClient}>
</QueryClientProvider>
You can enable or disable useQuery by your condition
like this :
export const useLiquidityPositions = (
userAddress: string,
enabled: boolean
): GraphQLResponse<LiquidityPositions> => {
const {
status: status,
isLoading: loading,
error: error,
data: response,
} = useQuery({
queryKey: ['USER_POSITIONS', userAddress],
queryFn: async () => {
if (!isAddress(userAddress)) {
return undefined;
}
const res = await getLiquidityPositionsData(userAddress);
return res;
},
enabled,
});
const payload = response?.data;
return { status, loading, error, response, payload };
};
and in usage:
const result = useLiquidityPositions('...',true //your condition )

Call useSWR by previous useSWR data.map then got Error(useSWR to get google calendar event)

I write a hook named useGoogleCalendarEvents, Try to get users google calendar event,below is my code.
import { Schedule, User } from '../../types/commons.type';
import { useLocalStorage } from 'usehooks-ts';
import useSWR from 'swr';
import { FetchCalendarList } from '../../types/googleCalendarAPIResponse.type';
import { currentUTCMonthString } from '../../utils/DateUtils';
const baseURL = 'https://www.googleapis.com/calendar/v3';
const fetcher = ([path, method, body = {}, accessToken = '']: [string, string, object, string]) => {
const headers = {
'Content-Type': ['POST', 'PUT', 'PATCH'].includes(method) ? 'application/json' : '',
Authorization: `Bearer ${accessToken}`,
};
const fetchOptions = {
method,
headers,
body: method === 'GET' ? undefined : JSON.stringify(body),
};
const apiURL = `${baseURL}${path}`;
return fetch(apiURL, fetchOptions).then((r) => r.json());
};
export const useGoogleCalendarEvents = () => {
const [user, setUser] = useLocalStorage<User | undefined>('user', undefined);
const events: Array<string> = [];
if (user?.googleAccessToken) {
// get user calendar list
const { data: GoogleCalendarListResult } = useSWR<FetchCalendarList>(
['/users/me/calendarList', 'GET', {}, user?.googleAccessToken],
fetcher,
);
const { startString, endString } = currentUTCMonthString();
const parameters = `timeMin=${startString}&timeMax=${endString}`;
// map loop to get event by calendar id
GoogleCalendarListResult?.items?.map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
);
});
}
return { events };
};
the code not yet finish but i got the error
"React has detected a change in the order of Hooks called by MySettingPage2."
I modify the code
GoogleCalendarListResult?.items?.map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
); });
to
['calendarid1', 'calendarid2'].map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
);
});
It is work, but not flexble. I want to get calendar id list first, then get envets by calendar id
Is anybody known why cause this error? Thank for any reply

react-query custom hook how to use useMutation and useQuery conditionaly

i create custom hook for use useQuery() :
export const useRequest = (
{
path = "",
params = {},
body = {},
key = "",
options= {},
method = "get"
}
) => {
// async function for get API:
const callApi = async () => {
const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
const query = useQuery(key, callApi, {
refetchOnWindowFocus: false,
...options
});
return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}
To use the useMutation hook without using the separate function, I changed my useRequest() as follows:
export const useRequest = (
{
path = "",
params = {},
body = {},
key = "",
options= {},
method = "get",
mutation = false
}
) => {
// async function for get API:
const callApi = async () => {
const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
if (mutation) {
const callMutationApi = async (data) => {
const {params, body} = data;
const { data: { response } } = await axios.post(baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
return useMutation(callMutationApi, options);
}
const query = useQuery(key, callApi, {
refetchOnWindowFocus: false,
...options
});
return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}
But I get the following error:
React Hook "useMutation" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?
What changes should I make in the useRequest() function? Thank you for your guidance.
///// UPDATE /////
According to the answer of dear #kapobajza...
I changed the code as follows and it worked:
export const useRequest = (
{
path = "",
params = {},
body = {},
key = "",
options= {},
method = "get",
mutation = false
}
) => {
// async function for get API:
const callApi = async () => {
const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
const callMutationApi = async (data) => {
const {params, body} = data;
const { data: { response } } = await axios.post(baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
// Instead of returning here, just save the result in a variable
const useMutationResult = useMutation(callMutationApi, options);
const query = useQuery(key, callApi, {
refetchOnWindowFocus: false,
enabled: (!mutation && options?.enabled),
...options
});
// If mutation is defined, return that result
if (mutation) {
return useMutationResult;
}
return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}
I added the option part of the following code to useQuery().. This will prevent useQuery() from running:
{
refetchOnWindowFocus: false,
enabled: (!mutation && options?.enabled),
...options
}
Please tell me if this solution is correct?
Unfortunately you cannot call React hooks conditionally.
One thing you'll find out early adopting react is that you cannot have conditional hooks. This is because every hook is initially added into a list that is reviewed on every render cycle, so if the hooks don't add up, there is something amiss and any linter set up correctly will warn you.
Taken from this article
But the thing you could do is to return the result of the useMutation hook if mutation is defined:
export const useRequest = (
{
path = "",
params = {},
body = {},
key = "",
options= {},
method = "get",
mutation = false
}
) => {
// async function for get API:
const callApi = async () => {
const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
const callMutationApi = async (data) => {
const {params, body} = data;
const { data: { response } } = await axios.post(baseUrl._serviceUrl + path,
{
params,
...body
});
return response;
}
// Instead of returning here, just save the result in a variable
const useMutationResult = useMutation(callMutationApi, options);
const query = useQuery(key, callApi, {
refetchOnWindowFocus: false,
...options
});
// If mutation is defined, return that result
if (mutation) {
return useMutationResult;
}
return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}

useMutation status is always 'idle' and 'isLoading' always 'false' - react-query

So I use react-query to handle API requests. Current problem is that when i try to submit form data, post request, mutation state is always idle, and loading is always false. I also use zustand for state management.
This is useSubmitFormData hook. Post request executes as expected, just mutation status and isLoading does not get changed.
export const useSubmitFormData = () => {
const { postDataPlaceholder } = usePlaceholderApi();
// data which is submiting is getting from the store - reducer
const { data, filesToUpload } = useFormDataStore();
const { mutate, status, isLoading } = useMutation(() => postDataPlaceholder({ data }), {
onMutate: () => console.log('onMutate', status),
onSuccess: (res) => console.log(res),
onError: (err) => console.log('err', err),
});
return {
submitForm: mutate,
isLoading,
};
};
Now on FormPage.jsx it is triggered like this:
const { submitForm, isLoading } = useSubmitFormData();
const onSubmit = () => submitForm();
And this is how usePlaceholderApi looks like. It is kind of custom hook in purpose to use axios in combination with interceptors to handle authorization token.
const usePlaceholderApi = () => {
const { post } = usePlaceholderAxios();
return {
postDataPlaceholder: async (data) => post('/posts', { data }),
};
};
export default usePlaceholderApi;
And this is usePlaceholderAxios.
import axios from 'axios';
const usePlaceholderAxios = () => {
axios.interceptors.request.use(async (config) => {
const api = 'https://jsonplaceholder.typicode.com';
if (config.url.indexOf('http') === -1) {
// eslint-disable-next-line no-param-reassign
config.url = `${api}${config.url}`;
}
return config;
});
return {
get: (url, config) => axios.get(url, config),
post: (url, data, config) => axios.post(url, data, config),
};
};
export default usePlaceholderAxios;
Any ideas what could go wrong here ? Am I missing something ? Also tried to call axios directly in mutation, without usePlaceholderApi hook in between, but same outcome.

How to fetch API data from Axios inside the getServerSideProps function in NextJS?

I'm building an App with Next.js, and I need to connect to specific API routes (set up with API Platform) and populate pages with the route's responses.
The API is working fine, but no matter how I try to implement my Axios call inside the getServerSideProps, I always get the same error, ECONNREFUSED, from my Node stack.
I tried to get the data from useEffect() and it's working fine, but I would like to know if there's a way to call it directly in getServerSideProps.
I'm using a Node container for Docker, and the routes are authenticated through a JWT Token (stored in the session and the client cookies for the server-side connection)
Here are is my code:
pages/accounts.js:
export async function getServerSideProps(context) {
const cookies = new Cookies(context.req.headers.cookie)
const adminToken = cookies.get('jwtToken')
const res = await getAllAccounts(adminToken)
return {
props: {
testdata: ''
},
}
}
lib/accounts.js:
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`).then((response) => {
}).catch((error) => {
console.dir(error)
})
}
HTTP wrapper:
import axios from 'axios';
import jwt_decode from "jwt-decode";
import mockAdapter from 'axios-mock-adapter';
const service = ({ jwtToken = null, store = null, mockURL = null, mockResponse = null, multipart = false } = {}) => {
const options = {};
options.baseURL = process.env.NEXT_PUBLIC_API_URL + '/api';
if(multipart === true) {
options.headers = {
'Content-Type': 'multipart/form-data'
}
} else {
options.headers = {
'Content-Type': 'application/ld+json',
accept: 'application/ld+json'
}
}
const instance = axios.create(options);
instance.interceptors.response.use(response => {
return response;
}, error => {
return Promise.reject(error);
})
if (mockURL !== null && mockResponse !== null) {
let mock = new mockAdapter(instance);
mock.onAny(mockURL).reply(200, mockResponse)
}
return instance;
};
export default service;
Through the error dump in the node stack, I managed to see that the request headers are correct, and the JWT correctly passed through.
Do not use Axios. Just use fetch().
Next.js polyfills fetch() by default on both the client and server, so you can just use it:
In addition to fetch() on the client-side, Next.js polyfills fetch() in the Node.js environment. You can use fetch() in your server code (such as getStaticProps/getServerSideProps) without using polyfills such as isomorphic-unfetch or node-fetch.
Source.
getServerSideProps works well with axios if we return response.data
export const getServerSideProps: GetStaticProps = async ({ params }) => {
const { brandName } = params as IParams;
const brandData = await $host.get(`api/brand/${brandName}`).then(response => response.data);
return {
props: {
brand: brandData,
},
};
};
Your problem is that your async method does not return a promise.
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`);
return res;
}
In my NextJS begining I followed this tutorial , and I changed fetch to axios in this way:
export const getStaticPaths = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data = await res.json();
return {
props: { ninja: data },
};
};
I applied the change using useEffect()
useEffect(() => {
// const data = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// const res = await data.json();
// setninja(res);
const fetchData = async () => {
const result = await axios(`https://jsonplaceholder.typicode.com/users/${id}`);
setninja(result.data);
};
fetchData();
console.log(data);
}, []);
I hope this info will be useful for you.
I Used Axios in getServerSideProps without any problems.
export const getServerSideProps: GetServerSideProps = async({
params,
res
}) => {
try {
const response = await axios.get(`/api/test`);
return {
props: {
data: response.data
},
}
} catch {
res.statusCode = 404;
return {
props: {}
};
}
};

Resources