How to use API Route in next js? - reactjs

I am learning how to design API and at the same time how to use next.js API route.
I have set my first route api/property/[propertyId] that returns the specific property detail.
Now I am trying to set a dynamic route for the specific property id in the page folder page/property/[propertyId]. My issue is when I am getting directed on the specific page the data is not there as expected. I am receiving a response for error message.
Can someone point out what I did wrong, please?
pages>api>property>[propertyId]
export default function propertyHandler ({query : {propertyId} } ,res) {
var parser = new xml2js.Parser({explicitArray : false});
const data = fs.readFileSync(path.join(process.cwd(),'listing.xml'))
parser.parseString(data,function (err, results){
results = results.client.secondhandListing.property
const filteredProp = results.filter((property) => property.id === propertyId)
filteredProp.length > 0 ? res.status(200).json(filteredProp[0]) : res.status(404).json({message: `Property with id: ${propertyId} not found.` })
})
}
pages>property>[id].js
export const getDetails = async() => {
const res = await fetch(`${baseUrl}/api/property/[property.Id]}`)
const data = res.json()
return data
}
export async function getServerSideProps({params: { id } }) {
const data = await getDetails(`${baseUrl}/api/property/${id}`)
return {
props: {
propertyDetails: data
}
}
}

I got the answer to my mistake from somewhere else. It was my getdetails function that was wrong.
I have amended it to:
export const getDetails = async(baseUrl)=>{
const res = await fetch(baseUrl)
const data = await res.json()
return data
};
and it worked.

Related

How to make a request again depending on a variable change in react query?

I'm trying to make a request again depending on a variable change in react query ?
I'm getting a value from a query parameter in the url , and i'm looking to redo the useQuery if this value changes / exists ,
my useQuery function looks like this :
const { tag } = router.query;
const { isLoading, error, data } = useQuery(["articles"], () =>
getRecentArticles(tag ? `${tag}` : "")
);
my function looks like this :
export const getRecentArticles = async (tag:string) => {
if (tag.length === 0) {
const response = await axios.get(`${BASE_URL}/articles/?offset=0`)
return response.data;
} else {
const response = await axios.get(`${BASE_URL}/articles/?tag=${tag}?offset=0`)
return response.data;
}
};
but that doesn't really work if my tag variable changes , Any tips on how to do so ?
You can just pass it as query key next to articles
const { tag } = router.query;
const { isLoading, error, data } = useQuery(["articles", tag], () =>
getRecentArticles(tag ? `${tag}` : "")
);
you can read more about query keys in documentation. https://tanstack.com/query/v4/docs/guides/query-keys

NextJS get query outside component [duplicate]

This question already has an answer here:
How to access route parameter inside getServerSideProps in Next.js?
(1 answer)
Closed 10 months ago.
I have getServerSideProps function to get data from API:
export async function getServerSideProps() {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${queryCocktailName}`
)
const data = await res.json()
return {
props: { data },
}
}
I need to get query in this function, ( /search/queryCocktailName/ ) i tried const {query} = useRouter() but as NextJS say: Hooks can only be called inside of the body of a function component
This component location: search/[id].tsx
This should work:
import { GetServerSidePropsContext } from "next";
export async function getServerSideProps({
params
}: GetServerSidePropsContext<{ id: string }>) {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${params?.id}`
);
const data = await res.json();
return {
props: { data }
};
}
See docs. getServerSideProps gets context object as a first parameter and it contains params field which should contain an id field, because your file is named [id].tsx.
Edit: changed to TypeScript version.
Every getServerSideProps have context, which comprises of req and res.
export async function getServerSideProps({req, res}) {
const { url = "" } = req || {};
//url will have the query of your route like :: url = /search/queryCocktailName/
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${queryCocktailName}`
)
const data = await res.json()
return {
props: { data },
}
}
Above code will work for your use-case

ReactJs can't get async content form api call in useEffect and use it later in component

I'm lost. I've tried almost all I know. In my other component, similar process works fine, but in this one there is something obviously wrong implemented.
I have a Context Provider, and set two values to share, a function to call an Api and retrieve a list of contacts (getContactsList), and a variable where I put that list of contacts (contactsList).
I call getContactsList in useEffect. Later, I can use contactsList variable, but is always an empty array. I know that the problem is related to Promises and async functions maybe, but I can't find the mistake in my code.
I left here a copy of the components, starting for the view component that has the problem:
Detail Component:
function ContactDetail() {
const { getContactsList, contactsList } = useContext(ContactsContext);
const { id } = useParams();
useEffect(() => { getContactsList().catch(null) }, []);
const contact = contactsList?.filter(c => {
return (Number(c.id) === Number(id))
});
return (
<div>
{contact? "contact finded" : "contact not finded"}
</div>
);
}
ApiCall
async function apiCall (
url,
method = "get",
body,
headers)
{
try {
const response = await fetch(url, {
method,
body,
headers
});
const r = await response.json();
return r;
}
catch (err) {
Promise.reject(err);
}
}
Provider
function ContactsProvider({ children }) {
const [ contactsList,setContactsList ] = useState([]);
const getContactsList = async () => {
try {
const contactsResult = await apiCall("https://fakeurl.com");
setContactsList(contactsResult);
}
catch(err) {
setContactsList([]);
}};
return (
<ContactsContext.Provider value={{getContactsList, contactsList}}>
{children}
</ContactsContext.Provider>
);
}
Also, the code is in my GitHub: https://github.com/thenablyn/local-search-browser.git
This code doesn't seem to be in your repo, but I've tested what you've written and it seems to work, is there a chance the id coming from useParams isn't matching any of the contacts when you're filtering?
The error was that I forgot to get the first element when I did the array.filter() method. It's a Jr mistake.
The code in that assignment has to be like this:
const [contact] = contactsList.filter(...); //this get the first element and assign to constant "contact"

How can I ensure that the Next.js router.query is not undefined?

I'm using next.js and the import { useRouter } from 'next/router'.
However on initial load, router.query.id is undefined. It quickly fills in, but that initial load is a killer.
I'm trying to figure out how to do it, and tried:
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
// const res = await fetch(`https://.../posts/${params.id}`)
// const post = await res.json()
console.log(params)
// Pass post data to the page via props
return { props: { params } }
}
but this returns an error:
Error: getStaticPaths is required for dynamic SSG pages and is missing for '/app/d/[id]'.
I can't use getStaticPaths, since [id] is variable and can be any number of things.
So what's the best way to handle this?
I would do smt like this(without staticProps):
function Page(props) {
const router = useRouter();
const { query = {} } = router || {};
const { id = 0 } = query || {};
useEffect(()=> {
if(id) {
(async ()=> {
const res = await fetch(`https://.../posts/${id}`)
const post = await res.json();
})();
}
}, [id]);
}
And this is what official doc. says:
// You also have to define a Post component in the same file (pages/posts/[id].js)
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
return <h1>Posts are here</h1>;
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return { props: { post } }
}
UPDATE:
After a bit research, have figure out this solution with staticProps:
export default function Post({ post }) {
return <h1>Post is here</h1>;
}
export async function getStaticPaths() {
return {
paths: [
{ params: { id: '*' } }
],
fallback: true
};
}
export async function getStaticProps(context) {
const res = await fetch(`https://api.icndb.com/jokes/random/${context.params.id}`);
const post = await res.json()
return { props: { post } }
}

How to set route params for CRUD application using Redux and API server

I'm working on a React/Redux application that needs to make a simple GET request to an API server endpoint (/contents/{id}). Right now I have an action set up to fetch this data:
export const fetchInfoPage = id => {
return async dispatch => {
try {
const res = await fetch(`${server}/contents/${id}`)
if (res.ok) {
const json = await res.json()
await dispatch(fetchPageRequest())
await setTimeout(() => {
dispatch(fetchPageSuccess(json.data))
}, 1000)
} else {
const json = await res.json()
console.log(res, json)
}
} catch (error) {
dispatch(fetchPageFailure(error))
console.log(error)
}
}
}
And here's what fetchPageSuccess looks like:
const fetchPageSuccess = content => {
const { id, attributes } = content
return {
type: FETCH_PAGE_SUCCESS,
isFetching: false,
id: id,
name: attributes.name,
content: attributes.content,
created_by: attributes.created_by,
updated_by: attributes.updated_by,
created_at: attributes.created_at,
updated_at: attributes.updated_at
}
}
I am firing off this action inside of componentDidMount in my InfoPage component by using fetchInfoPage(match.params.name). The match.params.name is set up to match the parameters in the React Route (i.e. /:name/information). I want to instead change this to fetch the data by using an ID number from the JSON while still displaying :name as the route parameter.
I feel like I'm close in getting this wired up but there's a gap in my logic somewhere. Is it possible to do what I'm trying to do here? I also have access to a GET endpoint at /contents/slug/{slug}.
It's perfectly fine what you are trying to do.
Just map the id using your name in the fetchInfoPage from your json or you can actually send the id to your fetchInfoPage function from component. It has nothing to do with your route params. All you are doing is getting the name from your param and getting the corresponding id using your name. I assume you have a name: id map somewhere.
export const fetchInfoPage = name => {
return async dispatch => {
try {
const id = getIdFromName(name); // Write a helper function
const res = await fetch(`${server}/contents/${id}`)
if (res.ok) {
const json = await res.json()
await dispatch(fetchPageRequest())
await setTimeout(() => {
dispatch(fetchPageSuccess(json.data))
}, 1000)
} else {
const json = await res.json()
console.log(res, json)
}
} catch (error) {
dispatch(fetchPageFailure(error))
console.log(error)
}
}
}
Your route will still be /:name/information
What I ended up doing was fetching by slug instead. On the components where I fetched the data, I created the slug name in componentDidMount by using match.params.name from my route, then fired off fetchInfoPage(slugName) to get the data. I also cleaned up the code quite a bit so here's what fetchInfoPage looks like now:
export const fetchInfoPage = slug => {
return async dispatch => {
try {
dispatch(fetchPageRequest())
const res = await fetch(`${server}/contents/slug/${slug}`)
const contentType = res.headers.get('content-type')
if (contentType && contentType.includes('application/vnd.api+json')) {
const json = await res.json()
if (res.ok) {
dispatch(fetchPageSuccess(json))
} else {
printError(res, json)
dispatch(fetchPageFailure(res.body))
dispatch(push('/error'))
}
} else {
console.log('Not valid JSON')
dispatch(fetchPageFailure(res.body))
dispatch(push('/error'))
}
} catch (error) {
dispatch(fetchPageFailure(error))
dispatch(push('/error'))
console.log(`Network error: ${error.message}`)
}
}
}
And a componentDidMount example:
componentDidMount() {
const { match, fetchInfoPage } = this.props
const slugName = `${NAMESPACE}-${match.params.name}`
fetchInfoPage(slugName)
}

Resources