Next.js using SWR with axios - reactjs

I'm trying to use SWR to prefetch data in my project.
Here is my code:
export const getStaticProps = async (res) => {
const result = await axios.get(
`/orders/detail/${res.params.cid}/${res.params.oid}`
);
const orderDetailById = await result.data;
return {
props: { orderDetailById },
};
};
export const getStaticPaths = async () => {
const result = await fetch(`${server}/api/orders`);
const orders = await result.json();
const ids = orders.map((order_detail) => ({
oid: order_detail.oid,
cid: order_detail.cid,
}));
const paths = ids.map((id) => ({
params: { oid: id.oid.toString(), cid: id.cid.toString() },
}));
return {
paths,
fallback: false,
};
};
const fetcher = (url, params) => {
return fetch(url + params.cid + '/' + params.oid).then((r) => r.json());
};
const OrderDetailByOId = ({ orderDetailById }) => {
const cid = orderDetailById.customer[0].cid;
const oid = orderDetailById.detail[0].oid;
const params = useMemo(() => ({ cid, oid }), [cid, oid]);
const { data, error } = useSWR(['/orders/detail/', params], fetcher, {
initialData: orderDetailById,
});
if (error) {
console.log('errorHere', error);
return <div>failed to load</div>;
}
if (!data) return <div>Loading...</div>;
return <OrderDetailForm orderDetailById={orderDetailById} />;
};
export default OrderDetailByOId;
It works well in the first render.
At the same time, I didn't change any data in my database,
so when it renders the second time by refreshInterval:1000 it wouldn't change anything, but it popped up with some errors!
errorHere SyntaxError: Unexpected token < in JSON at position 0
When I first saw the error I guessed it was just some JSON problems, so I changed the fetcher's return like (r)=>r.data
After I changed this, it caused the web to return loading...
It means it didn't fetch anything in the second render or even each after the first render.
Did anyone can help me find out what problems caused the errors.
Thanks~

I forgot I have set Axios basic URl like Axios.defaults.baseURL = server + '/api';
so I changed the fetcher return like return axios.get(url + params.cid + '/' + params.oid).then((r) => r.data);
It works for me now~ Thanks for the #juliomalves pointing me out where could be a problem ~ Thanks!

Related

React-query useInfiniteQuery: getNextPageParam not working

I'm stuck using useInfiniteQuery.
The first call works fine, but the next page is not called with getNextPageParam
const getProductItems = async (par) => {
console.log("axios :", par);
const res = await axios.get(`/api/v1/products`, {
params: par,
});
return {
result: res.data,
};
};
export default function useGetProductItems(params) {
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
["getItems"],
({ pars = params }) => getProductItems(pars),
{
getNextPageParam: (res) => {
console.log(res);
const nextParams = {
...res.result.pageInfo,
page: res.result.pageInfo.page + 1,
};
console.log("next :", nextParams);
return nextParams;
},
select: (data) => {
return data.pages[0].result.data;
},
}
);
return {
data,
isLoading,
fetchNextPage,
hasNextPage,
isFetching,
};
}
And the Query Client setting is like this
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
queryCache: new QueryCache({
onError: errorHandler,
}),
mutationCache: new MutationCache({
onError: errorHandler,
}),
});
As I am new to react-query, I am also wondering if there is any data that must be received from the API.
plz answer for me
You can access pageParam and send it as argument to your fetching function. Also it'd be a good idea to check if there really is a next page before incrementing the actual page number in getNextPageParam. Something like this:
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
['getItems'],
({ pageParam = 1 }) => getProductItems(pageParam), // pageParam defaults to the first page
{
getNextPageParam: lastPage => {
return lastPage.result.pageInfo.page < lastPage.result.pageInfo.totalPages // Here I'm assuming you have access to the total number of pages
? lastPage.result.pageInfo.page + 1
: undefined // If there is not a next page, getNextPageParam will return undefined and the hasNextPage boolean will be set to 'false'
},
select: data => {
return data.pages[0].result.data
},
}
)
I don't have information about how is your API endpoint built, but typically the request should look, for example, like this:
const getProductItems = async (page) => {
const res = await axios.get(`/api/v1/products?page=${page}`);
return {
result: res.data,
};
};

Re-Usable fetch function with query string

I have a fetch function inside of my react component, which I wish to "outsourse" in a separate component.
export const fetchBooksBySubject = (selectedValue) => {
const options = {
method: `GET`,
};
fetch(`${server}/books?subjects_like=${selectedValue}`, options)
.then((response) => {
if(response.ok){
return response.json()
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data: ', error)
})
}
Basically selectedValue is a prop coming from a child of App.jsx. As soon as the value is selected in a component, fetch should fire with this value in a query string. I tried to export the function above as a component and use it in App.jsx
useEffect(() => {
fetchBooksBySubject(selectedValue).then(data => setBookList(data));
}, [selectedValue])
const handleChange = e => {
setSelectedValue(e);
fetchBooksBySubject(selectedValue);
};
But this throws Property 'then' does not exist on type 'void'.
Here's a custom hook you can use with fast and reusable data fetching, a built-in cache, and other features like polling intervals and revalidation.
Hook:
const useBooks = (selectedValue) =>
{
const fetcher = (...args) => fetch(...args).then(res => res.json())
const { data, error } = useSWR(`/api/books?subjects_like=${selectedValue}`, fetcher)
return {
books: data,
isLoading: !error && !data,
isError: error
}
}
Usage:
const { books, isLoading, isError } = useBooks(selectedValue)
if (isLoading) return <div>Loading...</div>
else return <div>Your content here</div>
swr docs
Without swr:
useEffect(() =>
{
const fetchData = async (selectedValue) =>
{
const books = await fetchBookBySubject(selectedValue)
setBookList(books)
}
fetchData(selectedValue)
}, [selectedValue, bookList])
So the problem was, that I wasn't returning my fetch. I am a beginner, so my understanding is, that my App.js just couldn't access the data from fetchBooksBySubject withot this return
const dev = process.env.NODE_ENV !== 'production';
const server = dev ? 'http://localhost:3001' : 'https://your_deployment.server.com';
// later definable for developement, test, production
export const FetchBooksBySubject = (selectedValue) => {
const options = {
method: `GET`,
};
return fetch(`${server}/books?subjects_like=${selectedValue}`, options)
.then((response) => {
if(response.ok){
return response.json()
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data: ', error)
})
}
Same as here:
let sum = (a,b) => {a+b}
sum(1,2) //undefined
let sum1 = (a,b) => {return a+b}
sum1(1,2) //3

React : waiting for a promise

i got a function that uses axios to get info from my backend as follows:
const getDoH = async () => {
const user = JSON.parse(localStorage.getItem("user"));
let config = {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + user.accessToken,
},
params: {
username: user.username,
},
};
return await axios.get(API_URL + "get", config);
};
Now i want to save these values into a global variable on initiate so i use the initialstate hook like this:
const [initialValues, setInitialValues] = useState(() => {
const initialSwitchState = getDoH().then(
(response) => {
setInitialValues(response.data);
},
(error) => {
console.log(error);
}
);
return initialSwitchState;
});
after that i got a function that takes the values from the db and maps them onto my local variable and this function looks like this:
const setStartValues = () => {
let newValues = initialSwitchState;
let valueArray = Object.entries(newValues).map((v, index) => {
v[1] = initialValues.switchValues[index]
return v
});
newValues = Object.fromEntries(valueArray);
setValues({...newValues});
}
and i want to call this function with a final function that is another initialstate hook like this:
const [values, setValues] = useState(() => {
const initialState = setStartValues();}
but by the time it gets to the line:
v[1] = initialValues.switchValues[index]
the initialValues is still a promise. and i cant see where i have gone wrong as i have used async and wait on my initial getDoH() function.
How can i solve this (wait for the promise) before i try to use the results?
kind regards.
There are two issues here:
First, you need to await getDoH() since that's an async function.
Second, useState() is a synchronous function, so you need to do the await getDoH() inside a useEffect() before you set const [initialValues, setInitialValues] = ...
Tbh i did it with use effect and it comes with its own set of issues. found the best way to do it was:
const [values, setValues] = useState(async () => {
const initialState = await getDoH().then(
(response) => {
let newValues = switchState;
let valueArray = Object.entries(newValues).map((v, index) => {
v[1] = response.data.switchValues[index]
return v
});
newValues = Object.fromEntries(valueArray);
setValues({...newValues});
},
(error) => {
console.log(error);
}
);
return initialState;
});

how to refactor duplicate API calls into a single API call?

I am pretty new to building full-stack applications, and I could like to avoid duplicating code in order to build the following to perform the calls in react my endpoints can be called like the following /api/v1/feeds/list/?page=${page} or api/v1/feeds/list/?search=${query} , but I would like to joing ?page=${page}&?search=${query} since search param is optional . I just want to make a single api call
async function fetchFeed(page) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}`);
}
async function searchQuery(query) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?search=${query}`);
}
const Main = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
const [feedCount, setfeedCount] = useState(0);
const [visible, setVisible] = useState(3)
const showMoreItems = () => {
setVisible(prevValue => prevValue + 3);
}
const browse = (page) => {
fetchFeed(page)
.then(function(response){
setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
// fetches data
const fetchData = (search) => {
searchQuery(search)
.then((response) => {
setFeed(response.data.results)
})
.catch((error) => {
console.log(error);
});
};
const handleSearch = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
browse(currentPage)
fetchData(feed);
}, [currentPage]);
}
I'd pass an object with both page and query, which both default to the empty string - and if empty, don't include them in the fetched URL:
async function fetchFeed({ page = '', query = '' }) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?${page ? `page=${page}&` : ''}${query ? `search=${query}` : ''}`);
}
If possible, make your API accept empty query parameters too, allowing you to simplify to
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}&query=${query}`);
Something like this should work for you
const fetchFeed = async (page, query) => {
let url =`http://localhost:8001/api/v1/feeds/list/?page=${page}`
if(query) url += `?search=${query}`
return api.get(url)
}
const browse = (page search) => {
await fetchFeed(page search)
.then(function(response){
!search && setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
useEffect(() => {
browse(currentPage) // just pass page
browse(currentPage, searchQuery); // pass both page and search query
}, [currentPage]);

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