NextJS Dynamic Rendering - reactjs

Long time developer finally picking up Next.js, so I know this is probably going to boil down to something silly. Here goes. What's wrong with my getStaticPaths() value here? It seems like I've formatted it exactly as the docs require. (Value being assigned to paths is console.log()'d in the terminal window)
export const getStaticPaths = async () => {
const paths = getEvents();
return {
paths,
fallback: false
};
};
And the getEvents() function:
export const getEvents = () => {
axios.post(`${globals.api_endpoint}getEvents.php`, {
action: 'getStaticPaths'
}).then((r) => {
if (!r.data.error) {
const paths = r.data.map(index => {
return {
params: {
id: index.event_id
}
};
});
console.log(paths);
return paths;
}
});
};

The getStaticPath is an async function. If you're doing something like this paths will always be a Promise here.
const paths = getEvents();
return {
paths,
fallback: false
};
You should use an await keyword here to wait for the results:
const paths = await getEvents();
and in the getEvents function you should return all the axios.post call, like so:
return axios.post(`${globals.api_endpoint}getEvents.php`, {...
Additionally, I don't know how your api endpoint looks but the api path should look like this: ${globals.api_endpoint}/getEvents.php. Your api endpoint shouldn't have the slash at the end.

Gorgeous. Thanks, #krybinski for the help. Of course it's returning a promise. The mistake wasn't quite as silly as I expected, but something simple, for sure.
export const getEvents = async () => {
return axios.post(`${globals.api_endpoint}getEvents.php`, {
action: 'getStaticPaths'
});
};
export const getStaticPaths = async () => {
const response = await getEvents();
const paths = response.data.map(event => {
return {
params: {
id: event.event_id
}
}
});
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

404 when requesting a single entry in contentful

I am trying to request a single entry from contentful CMS with the getEntry() method. I keep getting a 404. I really don't understand what am I doing wrong.
export async function getStaticProps({ params }) {
const p = params.id;
const client = contentful.createClient({
space: process.env.CONTENTFUL_SPACE_ID,
environment: process.env.CONTENTFUL_ENVIRONMENT_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
const entry = await client.getEntry(p);
return { props: { entry: entry } };
}
I created a small project to replicate the issue, but I couldn't. It works fine for me. This is what my sample code looks like:
import {createClient} from 'contentful'
export default function Comp() {
return (
<div>
Hello
</div>
)
}
export async function getStaticProps({ params }) {
const p = params.id;
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
environment: process.env.CONTENTFUL_ENVIRONMENT_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
const entry = await client.getEntry(p);
return { props: { entry: entry } };
}
export async function getStaticPaths() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
environment: process.env.CONTENTFUL_ENVIRONMENT_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
const entry = await client.getEntries({
content_type: "blogPost"
})
const paths = entry.items.map(item => {return {params: {id: item.sys.id}}});
return { paths, fallback:true}
}
You might be getting a 404 because the paths might be correctly configured. If the above code sample doesn't help, feel free to link the repo or share more details :)

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 } }
}

NextJS: How do I get exportPathMap page data? I don't see it in getStaticProps

I have set up exportPathMap but I get an empty object when exporting getStaticProps from my component. I don't understand how I'm supposed to access it?
// next.config.js
exportPathMap: async function (
defaultMapPath,
{ dev, dir, outDir, distDir, buildId }
) {
const paths = {}
const response = await fetch(...)
const data = await response.json()
data.forEach((item) => {
paths[`/item/${item.id}`] = {
page: '/itemPage',
query: {
item,
},
}
})
return paths
},
And
// itemPage.js
...
export async function getStaticProps(props) {
console.log('props', props) // returns "props {}""
return {
props: props
}
}
...
It's difficult to find it specifically stated in the documentation right now, but here are your options.
OPTION 1
Use getStaticPaths and getStaticProps like so (from "with-static-export" in the nextJS examples)
// [id].js post file
export async function getStaticPaths() {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts?_page=1'
)
const postList = await response.json()
return {
paths: postList.map(post => {
return {
params: {
id: `${post.id}`,
},
}
}),
fallback: false,
}
}
export async function getStaticProps({ params }) {
// fetch single post detail
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
)
const post = await response.json()
return {
props: post,
}
}
OPTION 2
Use exportPathMap AND getInitialProps
// item.js
...
Page.getInitialProps = function (context) {
return context.query.item
}
// next.config.js
module.exports = {
// ...Same as in your question...
}
Both will allow you to have static optimization. With Option 1, you need not export, next build is sufficient. For Option 2 you will need to run next export
From: https://nextjs.org/docs/advanced-features/static-html-export
If your pages don't have getInitialProps you may not need next export at all; next build is already enough thanks to Automatic Static Optimization.
Another note: You can use both methods inside a single project.

Rematch/Effects- Error running test cases written with react testing Library

I am trying to write unit test cases for my models using react testing library but i am facing some issues executing the test cases.
My effects.js
export async function getStoredData(param1, store, param2) {
try {
dispatch(setLoading(true, 'getStoredData'));
// Check if key exists in store
const inputKeyCode = getInputKeyCode([param1, param2]);
let response = getUserDataState(store)[inputKeyCode];
if (!response) {
response = await getUserApi(param1, param2);
this.setUserData({ keyCode: inputKeyCode, keyValue: response });
}
return response;
} catch (error) {
// dispatch error
} finally {
dispatch(setLoading(false, 'getStoredData'));
}
}
My reducers.js
const INITIAL_STATE = {
userData: {},
};
const setUserData = (state, { key, value }) => ({ // {key: value}
...state,
userData: {
...state.userData,
[key]: value,
},
});
effects.test.js
import { getUserApi } from '../../../api/common';
jest.mock('../../../store', () => ({ dispatch: jest.fn() }));
jest.mock('../../../api/common', () => ({ getUserApi: jest.fn() }));
describe('getStoredData', () => {
const responseData = {};
setWith(responseData, 'data.userInformation', 12345);
const setUserData = jest.fn();
test('success', async () => {
getUserApi.mockResolvedValue(responseData);
await testModel.effects().getStoredData.call({ setuserData });
expect(setuserData).toHaveBeenCalled();
expect(setuserData).toHaveBeenCalledWith(12345);
});
test('failure', async () => {
getUserApi.mockRejectedValue(errorMsg);
await testModel.effects().getStoredData.call({ setuserData });
expect(showNotification).toHaveBeenCalled();
expect(showNotification).toHaveBeenCalledWith('error');
});
});
This gives me below error-
Expected mock function to have been called, but it was not called.
At line- expect(setuserData).toHaveBeenCalled();
Can someone help me understand what i am doing wrong? I guess i am doing some mistake in calling the setuserData. Any help is much appreciated.

Resources