How to pass a dynamic value to getStaticPaths - reactjs

I have problem with sending a dynamic value to getStaticPaths. I want to change the url based on my context. But, is this possible i don't know. If not how can I reorganize my code. Here is the thing that I want to do which I point out below as 'query'. I know I can not use my context values within the getStaticPaths. So, how can I handle it?
export const getStaticPaths = async () => {
const url = `https://www.omdbapi.com/?apikey=[Mykey]=${query}`
const movies = await axios.get(url)
const data = movies.data.Search
const paths = data.map(movie => {
return {
params: {id: movie.imdbID}
}
})
return{
paths,
fallback:false
}
}

Related

All my TRPC queries fail with a 500. What is wrong with my setup?

I am new to TRPC and have set up a custom hook in my NextJS app to make queries. This hook is sending out a query to generateRandomWorker but the response always returns a generic 500 error. I am completely stuck until I can figure out this issue.
The hook:
// filepath: src\utilities\hooks\useCreateRandomWorker.ts
type ReturnType = {
createWorker: () => Promise<Worker>,
isCreating: boolean,
}
const useCreateRandomWorker = (): ReturnType => {
const [isCreating, setIsCreating] = useState(false);
const createWorker = async (): Promise<Worker> => {
setIsCreating(true);
const randomWorker: CreateWorker = await client.generateRandomWorker.query(null);
const createdWorker: Worker = await client.createWorker.mutate(randomWorker);
setIsCreating(false);
return createdWorker;
}
return { createWorker, isCreating };
Here is the router. I know the WorkerService calls work because they are returning the proper values when passed into getServerSideProps which directly calls them. WorkerService.generateRandomWorker is synchronous, the others are async.
// filepath: src\server\routers\WorkerAPI.ts
export const WorkerRouter = router({
generateRandomWorker: procedure
.input(z.null()) // <---- I have tried completely omitting `.input` and with a `null` property
.output(PrismaWorkerCreateInputSchema)
.query(() => WorkerService.generateRandomWorker()),
getAllWorkers: procedure
.input(z.null())
.output(z.array(WorkerSchema))
.query(async () => await WorkerService.getAllWorkers()),
createWorker: procedure
.input(PrismaWorkerCreateInputSchema)
.output(WorkerSchema)
.mutation(async ({ input }) => await WorkerService.createWorker(input)),
});
The Next API listener is at filepath: src\pages\api\trpc\[trpc].ts
When the .input is omitted the request URL is /api/trpc/generateRandomWorker?batch=1&input={"0":{"json":null,"meta":{"values":["undefined"]}}} and returns a 500.
When the .input is z.null() the request URL is /api/trpc/generateRandomWorker?batch=1&input={"0":{"json":null}} and returns a 500.
Can anyone help on what I might be missing?
Additional Info
The client declaration.
// filepath: src\utilities\trpc.ts
export const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: `${getBaseUrl() + trpcUrl}`, // "http://localhost:3000/api/trpc"
fetch: async (input, init?) => {
const fetch = getFetch();
return fetch(input, {
...init,
credentials: "include",
})
}
}),
],
transformer: SuperJSON,
});
The init:
// filepath: src\server\trpc.ts
import SuperJSON from "superjson";
import { initTRPC } from "#trpc/server";
export const t = initTRPC.create({
transformer: SuperJSON,
});
export const { router, middleware, procedure, mergeRouters } = t;
Sorry I am not familiar with the vanilla client. But since you're in react you might be interested in some ways you can call a trpc procedure from anywhere while using the react client:
By using the context you can pretty much do anything from anywhere:
const client = trpc.useContext()
const onClick = async () => {
const data = await client.playlist.get.fetch({id})
}
For a known query, you can disable it at declaration and refetch it on demand
const {refetch} = trpc.playlist.get.useQuery({id}, {enabled: false})
const onClick = async () => {
const data = await refetch()
}
If your procedure is a mutation, it's trivial, so maybe you can turn your GET into a POST
const {mutateAsync: getMore} = trpc.playlist.more.useMutation()
const onClick = async () => {
const data = await getMore({id})
}
Answered.
Turns out I was missing the export for the API handler in api/trpc/[trpc].ts

How to generate the list of route segment parameters for every pokemon using generateStaticParams in Next.js 13?

I am trying to use Nextjs 13's generateStaticParams to render static data into my web page. How do I generate the list of routes like /pokemon/[pokemonName] for every pokemon name using the generateStaticParams without providing hard-coded pokemon name (for example here I put "charmander" as argument for fetchData and then it generate route just for charmander)?
export const generateStaticParams = async (): Promise<PageParams[]> => {
const res = await fetchData("charmander");
return [
{
pokemonName: res?.data.pokemon.name,
},
];
};
Fetching pokemon moves from graphQL-pokeAPI:
const fetchData = async (pokemonName: string) => {
const POKEMON_MOVES = `
query pokemon($name: String!) {
pokemon(name: $name) {
id
name
sprites {
front_default
}
moves {
move {
name
}
}
}
}
`;
const res = await fetch("https://graphql-pokeapi.graphcdn.app/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: POKEMON_MOVES,
variables: { name: pokemonName },
}),
});
const moves = await res.json();
return moves;
};
export default function SpecificPokemon({ params }) {
const { pokemonName } = params;
const pokemonData = use(fetchData(pokemonName));
return (<h1>{pokemonName}</h1>...)
};
In the Nextjs13 beta docs, it said that generateStaticParams doesn't require any context parameters. So I can't pass pokemonName into generateStaticParams like this, right?
export const generateStaticParams = async (pokemonName: string) => {
const res = await fetchData(pokemonName);
I tried to just write fetchData("") and the page just blank. Also, it would be too many to write like this:
return [
{ pokemonName: "bulbasaur" },
{ pokemonName: "ivysaur" },
{ pokemonName: "venusaur" },
];
Also, is this due to my lack of JS / Next13 concept understanding?
They actually have a feature called getStaticPaths which was specifically designed for this. You can easily make a call to a pokemon api and parse the response into getStaticPaths at run to generate the static paths you want to use. I encourage you to play with the documentation more, that team did a killer job with them.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
})
);
}
Here's an example for a posts table - but basically you get all the posts or in your case Pokemon, and map the names to an array.
I'm not sure if there's a better way of handling it but that should point you in the right direction. I'm still reading up on NextJS and especially the new Beta myself, so take with a pinch of salt.

Data from function is null on first load

I'm using a function from another file to get data from a Firebase. However, the first time I load the app, I don't get the data. Data pulls it out fine. When I write to the console right after adding it to a variable, I see them. However, Return does not return them and returns only an empty field.
If I edit and save the file after loading, the application is refreshed and the data is loaded. Is this solution correct? Then I want to have more functions there.
const Page = () => {
const [categories, setCategories] = useState([]);
const fetchMainCategories = async () => {
const results = placesB.getMainCategories();
setCategories(results);
};
useEffect(() => {
fetchMainCategories();
}, []);
}
export default Page;
class Categories {
getMainCategories(){
let val = [];
const reference = query(ref(db, 'categories/1/'));
onValue(reference, (snapshot) => {
val = snapshot.val();
console.log(val); // It always lists the data here
});
return val; // HERE IS THE PROBLEM. On the first load is this empty!
}
}
const cats = new Categories();
export default cats;
Is here anyone who can help me please?
The onValue() returns data asynchronously and hence the array is always returned empty. Since you need to fetch data only once, use get() instead of onValue():
class Categories {
// async
async getMainCategories() {
const reference = query(ref(db, 'categories/1/'));
const snap = await get(reference);
return snap.val();
}
}
Then you call this method as shown below:
const fetchMainCategories = async () => {
// await here
const results = await placesB.getMainCategories();
console.log(results);
setCategories(results);
};
Try to put a condition, as if(val) then return val....

How to use API Route in next js?

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.

How to stop variable re initializing in context

I have a variable in my context with the base url for my api and i add parameters to that base call to add parameters, the problem that i have is that i can't concatenate more than one parameter at a time, so if for example set my checkbox for one brand the url updates to that brand filter and the function adds the brand filter parameter, but if i search something the url re-initialites itself and deletes the other parameter.
Some images for more clarity:
If i make a search:
The url console logs like this, the line 87 logs the url before i concatenate the parameter and the 91 after i concatenate the parameter:
And if i check the Nike brand that should only show the Nike Air Yeezy product and url should be "http://localhost:5000/api/v1/products?name=Yeezy&company=Nike" is only "http://localhost:5000/api/v1/products?company=Nike&"
My theory is that as i declarate the base url parameter every time i do one function the context resets and that url var re initializes, but how can i stop this? I searched at how to make a string state and concatenating that state but couldn't find anything.
Here is the part of the context where i handle the new api calls, if something seems weird/missing is because i cut the context to make it more clear
var url = "/api/v1/products?";
const [productList, setProductList] = useState([]);
const [brandFilter, setBrandFilter] = useState([]);
const [search, setSearch] = useState({
text: "",
});
const getProductList = async () => {
const response = await fetch(url);
const responseJson = await response.json();
if (responseJson) {
setProductList(responseJson.products);
}
};
useEffect(() => {
getProductList();
}, []);
useEffect(() => {
setSearchUrl();
getProductList();
}, [search]);
const setBrandUrl = () => {
console.log(url);
if (brandFilter.length > 0) {
for (var i = 0; i < brandFilter.length; i++) {
url += `company=${brandFilter[i]}&`;
}
}
console.log(url);
};
const setSearchUrl = () => {
console.log(url);
if (search.text != "") {
url += `name=${search.text}`;
}
console.log(url);
};
useEffect(() => {
setBrandUrl();
getProductList();
}, [brandFilter]);
It's because you've got the useEffect split into two; one which is reacting to the search changing, and one which is reacting to the brands changing. You need to combine those into one.
If we look at the effect for when the search changes, and we reduce the code to what's actually running, your effect is doing this:
useEffect(() => {
var url = "/api/v1/products?";
if (search.text != "") {
url += `name=${search.text}`;
}
getProductList();
}, [search]);
i.e. it ignores brandsFilter completely. You need to have both the brandsFilter and the search as a dependency for a single effect:
useEffect(() => {
let url = "/api/v1/products?";
if (search.text) {
url += `name=${search.text}`;
}
if (brandsFilter.length) {
const brands = brandsFilter.map(b => `&brand=${b}`).join('');
url += search.text ? brands : brands.substr(1);
}
// NOTE: pass the 'url' to the get as a parameter here!
getProductList(url);
}, [search, brandsFilter]);
Note: this is easier if you use a library like query-string to do the heavy lifting for you:
import qs from 'query-string';
useEffect(() => {
const url = qs.stringifyUrl({
url: "/api/v1/products?",
query: {
name: search.text || undefined,
brands: brandsFilter,
}
});
getProductList(url);
}, [search, brandsFilter]);

Resources