I am using nextJS SSR in my project. Now when I try to use the following code to get page parameters then it shows undefined.
function About() {
const router = useRouter();
const { plan_id } = router.query;
console.log(plan_id)
}
export default About;
It works when the page is routed from some other page (without page reload with "next/link") but it does not work when I refresh the page. Can someone please help?
I found the answer self. Actually when you refresh the page then the router does not get initialized instantly. So you can add that under UseEffect hook as following and you will be able to get the parameters
function About() {
const [param1, setParam1]=useState("");
const router = useRouter();
useEffect(() => {
if (router && router.query) {
console.log(router.query);
setParam1(router.query.param1);
}
}, [router]);
}
When this router parameter will change then it will call the "UseEffect" which can be used to retrieve the values.
function About({plan_id}) {
console.log(plan_id)
}
// this function only runs on the server by Next.js
export const getServerSideProps = async ({params}) => {
const plan_id = params.plan_id;
return {
props: { plan_id }
}
}
export default About;
You can find more intel in the docs.
I fix this problem with this method.
First add getServerSideProps to your page
//MyPage.js
export async function getServerSideProps({req, query}) {
return {
props: {
initQuery: query
}
}
}
Then created useQuery function like this
//useQuery.js
export let firstQuery = {}
export default function useQuery({slugKey = 'slug', initial = {}} = {}) {
const {query = (initial || firstQuery)} = useRouter()
useEffect(() => {
if (_.isEmpty(initial) || !_.isObject(initial))
return
firstQuery = initial
}, [initial])
return useMemo(() => {
if (!_.isEmpty(query)) {
return query
}
try {
const qs = window.location.search.split('+').join(' ');
const href = window.location.href
const slug = href.substring(href.lastIndexOf('/') + 1).replace(/\?.*/gi, '')
let params = {},
tokens,
re = /[?&]?([^=]+)=([^&]*)/g;
if (slug)
params[slugKey] = slug
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params
} catch {
}
}, [query])
}
And always use useQuery for receive query params
//MyPage.js
export default function MyPage({initQuery}) {
const query = useQuery({initial: initQuery})
return(
<div>
{query.myParam}
</div>
)
}
And in components like this
//MyComponent.js
export default function MyComponent() {
const query = useQuery()
return(
<div>
{query.myParam}
</div>
)
}
For those still having issues with this. Here is a solution that worked for me
function About() {
const [param1, setParam1]=useState("");
const router = useRouter();
const { param1 } = router.query()
useEffect(() => {
if (!param1) {
return;
}
// use param1
}, [param1]);
}
You can find the solution here
Related
On the initial loading of the Edit page, the query is returning as 'undefined' instead of the expected result. I am using client-side fetching and not using SSG or SSR. The 'Edit' page is located in the '/src/pages/solution/[id]/edit' directory.
Anyone, please tell me how I can fetch data using useDocument hook?
I tried to use isReady like this, but it's just showing a blank screen:
const SolutionEditForm = () => {
const [formData, setFormData] = useState(INITIAL_STATE)
const router = useRouter()
const { id, isReady } = router.query
if (!isReady) return null
const { document } = useDocument("solutions", id)
useEffect(() => {
if (document) {
setFormData(document)
}
}, [document])
return (
// JSX CODE
)
}
My code:
const SolutionEditForm = () => {
const [formData, setFormData] = useState(INITIAL_STATE)
const router = useRouter()
const { id } = router.query
console.log(id) // return undefined
const { document } = useDocument("solutions", id) // throws error because id is undefined
useEffect(() => {
if (document) {
setFormData(document)
}
}, [document])
return (
// JSX CODE
)
}
export default SolutionEditForm
Anyone, please help me with this!
isReady is part of the router object, not of router.query
Try router.isReady to validate if the router is already loaded.
I'm developing a blog using nextJS & sanity. And I connected sanity with nextJS and it's perfectly working in development mode. But when I try to deploy in Vercel or build through the VSCode, it shows the below error.
info - Generating static pages (0/8)TypeError: Cannot destructure property 'title' of 'post' as it is undefined.
Here is my component overview
export default function SinglePost({ post }) {
const {
title,
imageUrl,
publishedAt,
description,
topics,
rescources,
sourcecode,
body = [],
} = post;
return(
<div>
<h1>{title}</h1>
//remaining code....
</div>)
}
const query = groq`*[_type == "post" && slug.current == $slug][0]{
"title": title,
"imageUrl": mainImage.asset->url,
description,
"topics": topics[],
"rescources": rescources[],
"sourcecode": sourcecode,
"publishedAt": publishedAt,
body,
}`;
export async function getStaticPaths() {
const paths = await client.fetch(
`*[_type == "post" && defined(slug.current)][].slug.current`
);
return {
paths: paths.map((slug) => ({ params: { slug } })),
fallback: true,
};
}
export async function getStaticProps(context) {
const { slug = "" } = context.params;
const post = await client.fetch(query, { slug });
return {
props: {
post,
},
};
}
Hi i found this to work
const Page: NextPage = (props: any) => {
const { post = undefined || {} } = props
const { title = "Undefined title" } = post
return (
<>
<Head>
<title>{title}</title>
</Head>
</>
)
}
const query = groq`*[_type == "post" && slug.current == $slug][0]{
title,
"name": author->name,
"authorImage": author->image,
"categories": categories[]->title,
}`
export async function getStaticProps(context: any) {
// It's important to default the slug so that it doesn't return "undefined"
const { slug = '' } = context.params
const post = await client.fetch(query, { slug })
return {
props: {
post,
},
}
}
I was able to solve this problem.
Solution: Without destructuring 'title', I got a value through the direct access
<h1>{post.title}</h1>
i got the same issue, the error says prerender-error , I add a if block to handle the fallback :
const PostId = ({postId}) => {
const router = useRouter()
if (router.isFallback) {
return <div>Loading...</div>
}
return (
...
)
...
}
and it builds successfully, make sure you handle the fallback in page
I'm trying to restrict access to a admin area based on a int flag called isAdmin that is set to either 0 or 1 in my users table.
In the admin component I've made a function to fetch an API route that returns a unique user based on email, which will allow me to pass this parameter from the session to the API route - but it never returns true value
This is the code for the lookup function and how I restrict access in the component
export const getServerSideProps: GetServerSideProps = async () => {
const dashboards = await prisma.dashboard.findMany({
orderBy: {
id: "asc",
}
})
return {
props: JSON.parse(JSON.stringify({ dashboards })),
}
}
async function checkAdminUser(email: string) {
try {
const result = await fetch(`/api/user/${email}`, {
method: "GET",
})
const user = await result.json()
if (user.isAdmin == 1) {
return true
} else {
return false
}
} catch (error) {
console.error(error)
}
}
const Dashboard: React.FC<Props> = (props) => {
const { data: session, status } = useSession()
if (!session || !checkAdminUser(session.user?.email)) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
return (
<Layout>
..Layout code
</Layout>
)
}
I've also tried the checkAdminUser function as a Promise without success. The API route has been checked for valid output
"{"id":1,"image":null,"name":null,"email":"censoredfor#crawlers.com","emailVerified":null,"isAdmin":1,"createdAt":"2022-09-21T07:52:20.263Z","updatedAt":"2022-09-21T10:22:39.024Z"}"
Any tips to get me rolling would be greatly appreciated!
This answer assumes that console.log(user) gives you: {"id":1,"image":null,"name":null,"email":"censoredfor#crawlers.com","emailVerified":null,"isAdmin":1,"createdAt":"2022-09-21T07:52:20.263Z","updatedAt":"2022-09-21T10:22:39.024Z"}
Then you should better handle how you check if the user is an Admin. checkAdminUser returns a promise that is not resolved when you're checking for the value. A better solution would be to use react state to manage access to a specific component:
const Dashboard: React.FC<Props> = (props) => {
const { data: session, status } = useSession()
const [isAdmin, setAdmin] = useState(false);
const checkAdminUser = useCallback(async () => {
try {
const result = await fetch(`/api/user/${session?.user?.email}`, {
method: "GET",
})
const user = await result.json()
if (user.isAdmin == 1) {
setAdmin(true)
} else {
setAdmin(false)
}
} catch (error) {
console.error(error)
}
},[session?.user?.email])
useEffect(() => {
checkAdminUser()
},[checkAdminUser])
if (!session || !isAdmin) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
return (
<Layout>
..Layout code
</Layout>
)
}
Don't forget to: import {useCallback, useEffect, useState} from 'react'
I have a nextjs project that is using apollo graphql to fetch data from the backend. I am trying to render my page using server side rendering. But I am currently using graphql apollo hooks to fetch my data from the backend, and the react hooks prevents me from calling my backend inside of the getServerSideProps.
Create and fetch single page using graphql from Wordpress with clean URLs like services/[id].js
N.B: Warning Show ( Error: Response not successful: Received status code 500)
import {
gql,
ApolloClient,
InMemoryCache
} from "#apollo/client";
export const client = new ApolloClient({
uri: 'https://.........../graphql',
cache: new InMemoryCache()
});
const serviceDetail = (serviceOutput) => {
return (
<div>
{serviceOutput.serviceTitle}
{serviceOutput.serviceContent}
</div>
)
}
export const getServerSideProps = async (context) => {
const result = await client.query({
query: gql`
query serData($id: id!) {
HomePage: pageBy(uri: "https://......./home/") {
aboutSection {
serviceSec(id: $id) {
id
serviceTitle
serviceContent
serviceImage {
sourceUrl
}
}
}
}
}
`,
variables: {
id: context.params.id
}
})
return {
props: {
serviceOutput: result.data.HomePage.aboutSection.serviceSec;
},
};
}
export default serviceDetail;
i am not an expert, but as far i have used. you cannot use Apollo together with next js fetching method(ssg,ssr,isr).
Apollo runs queries on client side, and can be used with useQuery and useLazyQuery. while next js fetching is completely different.
I will demonstrate 2 ways here.
-- Using Apollo --
const FETCH_ALL = gql`
query MyQuery($first: Int!, $after: String) {
posts(first: $first, after: $after) {
edges {
node {
title
}
}
}
}
`;
export default function LoadMoreList() {
const { data } = useQuery(FETCH_ALL, {
variables: { first: 5, after: null },
notifyOnNetworkStatusChange: true,
});
return (
<>
<div>
{postdata.map((node, index) => {
{
return (
<div key={index}>
<h1>{node?.node?.title}</h1>
</div>
);
}
})}
</div>
</>
)}
=== using fetch and getStaticProps ==
--File1 (this is a fetch function, to which you pass your queries and variables)
async function fetchAPI(query, { variables } = {}) {
const headers = { "Content-Type": "application/json" };
const res = await fetch(process.env.WP_API, {
method: "POST",
headers,
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
console.log(json.errors);
throw new Error("Failed to fetch API");
}
return json.data;
}
export default fetchAPI;
-- File2 (this is a file that contains your query)
import fetchAPI from "./fetching";
export async function homeheadposts() {
const data = await fetchAPI(
`
query homeheadposts {
posts(first: 7) {
edges {
node {
id
slug
title
featuredImage {
node {
sourceUrl
}
}
excerpt(format: RAW)
}
}
}
}
`
);
return data?.posts;
}
-- File3 (place this function , where you wanna call and use the data, )
export async function getStaticProps() {
const latestPosts = await homeheadposts();
return {
props: { latestPosts },
};
}
export default function CallingData({ latestPosts }) {
console.log(latestPosts);
return <h1>hello</h1>;
}
Background to the Question
Vercel recently released their biggest update ever to Next.js. Next.js blog.
They introduced a lot of new features but my favorite is Middleware which:
"enables you to use code over configuration. This gives you full
flexibility in Next.js because you can run code before a request is
completed. Based on the user's incoming request, you can modify the
response by rewriting, redirecting, adding headers, or even streaming
HTML."
The Question
The following structure is used in this question.
- /pages
index.js
signin.js
- /app
_middleware.js # Will run before everything inside /app folder
index.js
The two important files here are /app/_middleware.js and /app/index.js.
// /app/_middleware.js
import { NextResponse } from 'next/server';
export function middleware(req, event) {
const res = { isSignedIn: true, session: { firstName: 'something', lastName: 'else' } }; // This "simulates" a response from an auth provider
if (res.isSignedIn) {
// Continue to /app/index.js
return NextResponse.next();
} else {
// Redirect user
return NextResponse.redirect('/signin');
}
}
// /app/index.js
export default function Home() {
return (
<div>
<h1>Authenticated!</h1>
// session.firstName needs to be passed to this file from middleware
<p>Hello, { session.firstName }</p>
</div>
);
}
In this example /app/index.js needs access to the res.session JSON data. Is it possible to pass it in the NextResponse.next() function or do you need to do something else?
In express you can do res.locals.session = res.session
According to the examples (look specifically at /pages/_middleware.ts and /lib/auth.ts) it looks like the canonical way to do this would be to set your authentication via a cookie.
In your middleware function, that would look like:
// /app/_middleware.js
import { NextResponse } from 'next/server';
export function middleware(req, event) {
const res = { isSignedIn: true, session: { firstName: 'something', lastName: 'else' } }; // This "simulates" a response from an auth provider
if (res.isSignedIn) {
// Continue to /app/index.js
return NextResponse.next().cookie("cookie_key", "cookie_value"); // <--- SET COOKIE
} else {
// Redirect user
return NextResponse.redirect('/signin');
}
}
There's a another way but just like using cookie to achieve this. Just pass you data through headers.
// middleware.ts
async function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('X-HEADER', 'some-value-to-pass');
return response;
}
// _app.ts
function MyApp({ data }) {
// you can access your data here
<div>{data}</div>
}
MyApp.getInitialProps = ({ ctx }) => {
const data = ctx.res.getHeader('X-HEADER');
ctx.res.removeHeader('X-HEADER');
return { data };
};
Only weird solution is to inject your custom object into req.body because next.js v12 middleware doesn't allow altering the NextApiRequest
export const middleware = async (req: NextApiRequest) => {
// return new Response("Hello, world!");
req.body = { ...req.body, foo: "bar" };
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await middleware(req);
// now req.body.foo=='bar'
}
They do however explain how you can extend middleware here, but the example given (copied below) isn't meaningful enough because it doesnt show how withFoo() is implemented
import { NextApiRequest, NextApiResponse } from 'next'
import { withFoo } from 'external-lib-foo'
type NextApiRequestWithFoo = NextApiRequest & {
foo: (bar: string) => void
}
const handler = (req: NextApiRequestWithFoo, res: NextApiResponse) => {
req.foo('bar') // we can now use `req.foo` without type errors
res.end('ok')
}
export default withFoo(handler)
I assumed based on the above, withFoo.ts should be like this. But still wasn't successful in accessing request.Foo()
import { NextApiHandler, NextApiRequest } from "next";
export const withFoo = (handler: NextApiHandler) => {
//do stuff
};
Maybe someone can chip in?
We found a solution for 12.2+ middleware - published here:
https://clerk.dev/blog/nextjs-pass-value-from-middleware-to-api-routes-and-getserversideprops
And copying here for posterity...
Usage: middleware.js
import { NextResponse } from "next/server";
import { withContext } from "./context";
// Pre-define the possible context keys to prevent spoofing
const allowedContextKeys = ["foo"];
export default withContext(allowedContextKeys, (setContext, req) => {
setContext("foo", "bar");
return NextResponse.next();
});
Usage: API route (Node)
import { getContext } from "../../context";
export default function handler(req, res) {
res.status(200).json({ foo: getContext(req, "foo") });
}
Usage: API route (Edge)
import { getContext } from "../../context";
export default function handler(req) {
return new Response(JSON.stringify({ foo: getContext(req, "foo") }));
}
Usage: getServerSideProps (Edge and Node)
import { getContext } from "../context";
export const getServerSideProps = ({ req }) => {
return { props: { foo: getContext(req, "foo") } };
};
Source: (saved to context.js on your root)
import { NextResponse } from "next/server";
const ctxKey = (key) => `ctx-${key.toLowerCase()}`;
export const getContext = (req, rawKey) => {
const key = ctxKey(rawKey);
let headerValue =
typeof req.headers.get === "function"
? req.headers.get(key) // Edge
: req.headers[key]; // Node;
// Necessary for node in development environment
if (!headerValue) {
headerValue = req.socket?._httpMessage?.getHeader(key);
}
if (headerValue) {
return headerValue;
}
// Use a dummy url because some environments only return
// a path, not the full url
const reqURL = new URL(req.url, "http://dummy.url");
return reqURL.searchParams.get(key);
};
export const withContext = (allowedKeys, middleware) => {
// Normalize allowed keys
for (let i = 0; i < allowedKeys.length; i++) {
if (typeof allowedKeys[i] !== "string") {
throw new Error("All keys must be strings");
}
allowedKeys[i] = ctxKey(allowedKeys[i]);
}
return (req, evt) => {
const reqURL = new URL(req.url);
// First, make sure allowedKeys aren't being spoofed.
// Reliably overriding spoofed keys is a tricky problem and
// different hosts may behave different behavior - it's best
// just to safelist "allowedKeys" and block if they're being
// spoofed
for (const allowedKey of allowedKeys) {
if (req.headers.get(allowedKey) || reqURL.searchParams.get(allowedKey)) {
throw new Error(
`Key ${allowedKey.substring(
4
)} is being spoofed. Blocking this request.`
);
}
}
const data = {};
const setContext = (rawKey, value) => {
const key = ctxKey(rawKey);
if (!allowedKeys.includes(key)) {
throw new Error(
`Key ${rawKey} is not allowed. Add it to withContext's first argument.`
);
}
if (typeof value !== "string") {
throw new Error(
`Value for ${rawKey} must be a string, received ${typeof value}`
);
}
data[key] = value;
};
let res = middleware(setContext, req, evt) || NextResponse.next();
// setContext wasn't called, passthrough
if (Object.keys(data).length === 0) {
return res;
}
// Don't modify redirects
if (res.headers.get("Location")) {
return res;
}
const rewriteURL = new URL(
res.headers.get("x-middleware-rewrite") || req.url
);
// Don't modify cross-origin rewrites
if (reqURL.origin !== rewriteURL.origin) {
return res;
}
// Set context directly on the res object (headers)
// and on the rewrite url (query string)
for (const key in data) {
res.headers.set(key, data[key]);
rewriteURL.searchParams.set(key, data[key]);
}
// set the updated rewrite url
res.headers.set("x-middleware-rewrite", rewriteURL.href);
return res;
};
};