I am using #react-keycloak/ssr with latest next.js, just started so project as clean as possible, all I really have are installed dependencies and _app.tsx with index.tsx from examples.
_app.tsx is identical copy (except url to keycloak) of official github example and index.tsx is next:
import { useKeycloak } from '#react-keycloak/ssr'
import {KeycloakInstance, KeycloakTokenParsed} from 'keycloak-js'
type ParsedToken = KeycloakTokenParsed & {
email?: string
username?: string
}
export default function Index() {
const { keycloak } = useKeycloak<KeycloakInstance>()
const parsedToken: ParsedToken | undefined = keycloak?.tokenParsed
const state = keycloak?.authenticated ? <span>{parsedToken?.username}</span> : <span>'Undefined'</span>;
function handleLoginButtonClick() {
if (keycloak) {
window.location.href = keycloak.createLoginUrl()
}
}
return (
<div>
{state}
<button className="btn btn-blue" onClick={() => handleLoginButtonClick()}>Login</button>
</div>
)
}
And my problem is that after a login I am getting errors
Warning: Text content did not match. Server: "" Client: "'Undefined'"
at span
at div
at Index (webpack-internal:///./pages/index.tsx:18:84)
I've tried to implement state change using useEffect but then keycloak?.authenticated is always false,
let [state] = useState('No user');
useEffect(() => {
state = keycloak?.authenticated ? 'User' : 'No user';
}, []);
then I tried to use getServerSideProps, but then I get an error that useKeycloak hook can be used inside a function only.
What else can I try?
p.s. Short gif/video of what is happening https://imgur.com/a/c2q6ftU
Found a solution by tweaking useEffect slightly:
const [username, setUsername] = useState('unknown')
useEffect(() => {
if (keycloak?.authenticated) {
setUsername(parsedToken?.email)
}
}, [keycloak?.authenticated])
Related
New to trpc. Trying to get basic query functionality but it's not working. Not sure what I'm missing. In v9 it used createReactQueryHooks(), but it seems in v10 you only need to use createTRPCNext() if I'm not mistaken inside util/trpc.tsx.
Error:
next-dev.js:32 Error: Query data cannot be undefined - affected query key: ["greeting"]
at Object.onSuccess (query.mjs:320:19)
at resolve (retryer.mjs:64:50)
// utils/trpc.ts
export const trpc = createTRPCNext<AppRouter, SSRContext>({
config({ ctx }) {
return {
transformer: superjson, // optional - adds superjson serialization
links: [
httpBatchLink({
/**
* If you want to use SSR, you need to use the server's full URL
* #link https://trpc.io/docs/ssr
**/
url: `${getBaseUrl()}/api/trpc`,
}),
],
/**
* #link https://react-query-v3.tanstack.com/reference/QueryClient
**/
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
headers() {
if (ctx?.req) {
// To use SSR properly, you need to forward the client's headers to the server
// This is so you can pass through things like cookies when we're server-side rendering
// If you're using Node 18, omit the "connection" header
const {
// eslint-disable-next-line #typescript-eslint/no-unused-vars
connection: _connection,
...headers
} = ctx.req.headers;
return {
...headers,
// Optional: inform server that it's an SSR request
"x-ssr": "1",
};
}
return {};
},
};
},
ssr: true,
});
// server/router/_app.ts
import { t } from '#/server/trpc';
import { userRouter } from '#/server/router/user';
import { postRouter } from '#/server/router/posts';
import { authRouter } from './authy';
export const appRouter = t.router({
user: userRouter,
post: postRouter,
authy: authRouter,
greeting: t.procedure.query(() => 'hello tRPC v10!'),
});
export type AppRouter = typeof appRouter;
// server/router/authy.ts
import { t } from "#/server/trpc";
import * as trpc from "#trpc/server";
import { z } from "zod";
export const authRouter = t.router({
hello: t.procedure
// using zod schema to validate and infer input values
.input(
z.object({
text: z.string().nullish(),
})
.nullish().optional()
)
.query(({ input }) => {
return {
greeting: `hello ${input?.text ?? "world"}`,
};
}),
});
export type AuthRouter = typeof authRouter;
None of the routes work. They all show a similar error.
// pages/test.tsx
import React from "react";
import { NextPage } from "next";
import { trpc } from "#/utils/trpc";
const TestPage: NextPage = () => {
const helloNoArgs = trpc.authy.hello.useQuery();
const helloWithArgs = trpc.authy.hello.useQuery({ text: "client" });
const greeting = trpc.greeting.useQuery();
return (
<div>
<h1>Hello World Example</h1>
<ul>
<li>
helloNoArgs ({helloNoArgs.status}):{" "}
<pre>{JSON.stringify(helloNoArgs.data, null, 2)}</pre>
</li>
<li>
helloWithArgs ({helloWithArgs.status}):{" "}
<pre>{JSON.stringify(helloWithArgs.data, null, 2)}</pre>
</li>
<li>
greeting ({greeting.status}):{" "}
<pre>{JSON.stringify(greeting.data, null, 2)}</pre>
</li>
</ul>
</div>
);
};
export default TestPage;
It seems you are using superjson. You need to add superjson transformer at initTRPC.
routers/router/_app.ts
import { initTRPC } from '#trpc/server';
import superjson from 'superjson';
export const t = initTRPC.create({
transformer: superjson,
});
more detailed instruction can be found here: TRPC v10
Omgosh... it was because I was using "^10.0.0-proxy-beta.7" and not "^10.0.0-proxy-beta.8"
Edit: Somehow I had another error and encountered my own question again 22 days later and solved it again. In general, when using trpc it seems updating to all the #next packages is best as it seems to be somewhat easy to have packages not talking to each other as they improve.
https://trpc.io/docs/v10/quickstart#installation-snippets
On my side I was using a mocked trpc server that was not using superjson (whereas the real one was). I just used superjson.serialize(...) before adding the JSON body to my response (in my server mock), then it worked :)
I am using use-sse (server side rendering hook) in my next.js application but i am getting an error of "There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering."
The code is all similar to the link https://github.com/kmoskwiak/useSSE. Unfortunately, I am unable to predict the error of hydration in next.js application. What's the issue in it?
Here's is the code:
import { useSSE } from "use-sse";
import React from 'react';
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
export default function Todo() {
const [todos, error] = useSSE(() : Promise<Todo[]>=> {
return fetch("https://jsonplaceholder.typicode.com/todos").then(
(response: Response) => response.json()
);
}, []);
console.log(todos);
if (error) return <div>{error.message}</div>;
if (!todos?.length) return <span>Todo is Loading...</span>;
return (
<section>
<h1>Todo List</h1>
<ul>
{todos.map((todo: Todo) => {
return (
<li key={todo.id}>
<h6>{todo.title}</h6>
</li>
);
})}
</ul>
</section>
);
}
Its not even detecting the use-sse package now. Is there any other method to use this hook i.e use-sse?
One solution noted here: https://www.joshwcomeau.com/react/the-perils-of-rehydration/#the-solution
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
This may also be helpful: https://nextjs.org/docs/messages/react-hydration-error
I have a blog that is using getStaticProps + getStaticPaths to generate pages from headless WP - all good. Then I have a component that is connecting to the database to allow me to link to the next post in any within any individual category. This component is then used inside the ...postSlug page.
So basically the component works perfectly if I switch to using getServerSideProps for posts, as it updates the 'previous' + 'next' URLs as each post is requested and built on the server. However, if I use getStaticProps the component doesn't update with the correct URLs unless I manually refresh the page. So I need to use some client-side data fetching within the component, with useState, useEffect or SWR or something - but I'm not sure about the best way to do it (or even if that would work or if I should just use gServerSideProps).
Any help would be greatly appreciated. Thanks! Component below edited for clarity ...
export default function MovePost() {
const { usePost } = client
const post = usePost()
const { query = {} } = useRouter()
const { categorySlug } = query
const currentPaginationCursor = btoa( `arrayconnection:${post.databaseId}` )
const { posts } = client.useQuery()
const previous = posts({
first: 1,
before: currentPaginationCursor,
where: { categoryName: `${categorySlug}` },
}).nodes[0]?.slug
return (
<>
{ previous &&
<Link href={`/archive/${categorySlug}/${previous}`}>
<a ...
export default function Page() {
const { usePost } = client
const post = usePost()
return (
<>
<Layout>
<div dangerouslySetInnerHTML={{ __html: post?.content() ?? '' }} />
<MovePost />
</Layout>
</>
)
}
export async function getStaticProps(context: GetStaticPropsContext) {
return getNextStaticProps(context, {
Page,
client,
notFound: await is404(context, { client }),
})
}
export function getStaticPaths() {
return {
paths: [],
fallback: 'blocking',
}
}
How can I get the previous URL in Next.js?
I thought the values this.props.router.asPath and nextProps.router.asPath are different.
Actually, I want to call router.push after login. I know that router.back goes to the previous page. But it's possible to go to another site. The users having history stacks go to the previous page, the users not having history stacks go to / main page.
import { Component } from 'react'
import App, { Container } from 'next/app';
import ErrorComponent from '#/components/error'
export default class MyApp extends App {
render() {
console.log(this.props)
const { Component, pageProps, router } = this.props;
const props = {
...pageProps,
router
}
return (
<ErrorBoundary>
<Container>
<Component {...props} />
</Container>
</ErrorBoundary>
);
}
componentWillReceiveProps(nextProps) {
// previous page url /contents
console.log(this.props.router.asPath) // /about
console.log(nextProps.router.asPath) // /about
console.log('window.history.previous.href', window.history.previous) // undefined
}
}
How can I fix it? Or how can I get the previous URL to move page after login?
You find the Referer ( so the previous URL ) in the context of getServerSideProps or any other Data fetching methods
as
context.req.headers.referer
example in code
export async function getServerSideProps(context) {
console.log(context.req.headers.referer)
}
I've used Context do to this
In _app.tsx
import { HistoryProvider } from '../contexts/History'
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<ThemeProvider theme={theme}>
<Header />
<HistoryProvider>
<Component {...pageProps} />
</HistoryProvider>...
/contexts/History.tsx
import { useRouter } from 'next/router'
import React, { createContext, useState, useEffect, useContext } from 'react'
interface HValidation {
history: string[]
setHistory(data: string[]): void
back(): void
}
const HistoryContext = createContext<HValidation>({} as HValidation)
export const HistoryProvider: React.FC = ({ children }) => {
const { asPath, push, pathname } = useRouter()
const [history, setHistory] = useState<string[]>([])
function back() {
for (let i = history.length - 2; i >= 0; i--) {
const route = history[i]
if (!route.includes('#') && route !== pathname) {
push(route)
// if you want to pop history on back
const newHistory = history.slice(0, i)
setHistory(newHistory)
break
}
}
}
useEffect(() => {
setHistory(previous => [...previous, asPath])
}, [asPath])
return (
<HistoryContext.Provider
value={{
back,
history,
setHistory,
}}
>
{children}
</HistoryContext.Provider>
)
}
export function useHistory(): HValidation {
const context = useContext(HistoryContext)
return context
}
In any component, you can use
import { useHistory } from '../../contexts/History'
const ContentHeader: React.FC<ContentHeaderProps> = ({ title, hideBack }) => {
const { history, back } = useHistory() ...
I've used this component to back history ignoring links with hash (#), because the native router.back() was bugging when i have <a href="#someid" /> to scroll page to some page ids
I wanted to go back to last page, and not the last anchor
EDIT 01/04/2021
You can also set a fallback route for "back".
back(fallbackRoute?: string): void
function back(fallbackRoute?: string) {
for (let i = history.length - 2; i >= 0; i--) {
const route = history[i]
console.log({ route, pathname })
if (!route.includes('#') && route !== pathname) {
push(route)
const newHistory = history.slice(0, i)
setHistory(newHistory)
return
}
}
if (fallbackRoute) {
router.push(fallbackRoute)
}
}
I think you can implement a custom history in global state
Something like this
_app.js
import React from 'react';
import App, { Container } from 'next/app';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
state = {
history: [] // keep history items in state
};
componentDidMount() {
const { asPath } = this.props.router;
// lets add initial route to `history`
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
componentDidUpdate() {
const { history } = this.state;
const { asPath } = this.props.router;
// if current route (`asPath`) does not equal
// the latest item in the history,
// it is changed so lets save it
if (history[history.length - 1] !== asPath) {
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Component history={this.state.history} {...pageProps} />
</Container>
);
}
}
export default MyApp;
so then in your components you can navigate wherever you want within history
if (!history || !history.length) {
router.push('/');
} else {
router.push(history[history.length - 1]);
}
Hope this helps!
I was looking for a very simple way to do this since some of the answers here seem a bit complex for implementing something this simple. router.back() doesn't seem to work well in this scenario as, in my case, it'd go all the way back and out of my site some times.
So, I thought 🤔, what better way to do this than localStorage?
when I need to send the user to the '/login' route, I add the current route to localStorage
if (!auth.user) {
window.localStorage.setItem("path", router.asPath);
router.replace("/login");
return <div> redirecting to login... </div>;
}
and once the user sign-in, I send them back to the previous page (the route of which has been saved in the localStorage
if (auth.user) {
router.replace(localStorage.getItem("path") || "/");
return <div> Loading... </div>
);
}
You can observe the localStorage while testing to see what is going on. I hope this helps someone.
Let's say there's a /profile page which should be rendered iff user is logged in or else user should be redirected to /login, after user login on /login, it should be pushed to previous page (here/profile) but not on another website or New Tab.
In /profile this is how you should redirect to /login
Router.push('/login?referer=profile', '/login')
In /login after user is successfully logged in, use:
Router.push(Router.query.referer?.toString||'/')
Hope this helped.
I recently had this problem and used the following solution to route back to the previous page.
In my component I used the useRouter() hook from Next.js. This hook produces a router object which has the back() function. This function can be used on an <a> tag to redirect back in the following way.
const Component: React.FC = () => {
const router = useRouter();
return (
<>
<a onClick={() => router.back()}>Go back to the last page</a>
</>
);
};
Note that this function does not produce a URL that you can use as a value in the href, which is unfortunate. But I think this solution is simple yet effective.
Reference: https://nextjs.org/docs/api-reference/next/router#routerback
Simple Hook
Add this hook, and call it in your _app.tsx or where needed. You can compare it to router.pathname if you need to know what the change was.
const usePreviousRoute = () => {
const { asPath } = useRouter();
const ref = useRef<string | null>(null);
useEffect(() => {
ref.current = asPath;
}, [asPath]);
return ref.current;
};
This doesn't leave data behind once the page is closed (storage, cookies) and cleanly resets to null on next visit to the site.
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
// in _app.js
function MyApp({ Component, pageProps }) {
const router = useRouter();
const [history, setHistory] = useState({ previous: null, current: router.asPath });
useEffect(() => {
setHistory((oldHistory) => ({ ...oldHistory, previous: oldHistory.current, current: router.asPath }));
}, [router.asPath]);
return (<Component {...{ ...pageProps, history }} />);
}
export default MyApp;
// in a page
function MyPage({ ...pageProps }) {
return (<span>Previous route: {pageProps.history?.previous || "/"}</span>);
}
export default MyPage;
I tried doing similar to iurii's answer. My _app.js looks like this (I was trying to integrate with segment.com so felt this need)
export default class MyApp extends App {
componentDidMount () {
const { asPath } = this.props.router;
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
const isBrowser = typeof window !== 'undefined';
if(isBrowser) {
// For the first page load
console.log("Going to log first load --> referrer : ", document.referrer);
// this can get me the document.referrer properly, if I come to the website from a third party source like google search.
global.analytics.page(window.location.href,
{referrer: document.referrer}
)
}
}
static async getInitialProps ({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
state = {
history: [] // keep history items in state
};
componentDidUpdate() {
const { history } = this.state;
const { asPath } = this.props.router;
// if current route (`asPath`) does not equal
// the latest item in the history,
// it is changed so lets save it
if (history[history.length - 1] !== asPath) {
global.analytics.page(window.location.href, {
referrer: history[history.length - 1] ? history[history.length - 1] : ""
})
// this simulates the document.referrer on pages after the user navigates
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
}
So with a combination of history[history.length - 1] ? history[history.length - 1] : "" and const isBrowser = typeof window !== 'undefined'; I am able to simulate document.referrer for all cases. But I am missing one case, suppose , I am at google, my site landing page is A, then A points to B
Then google to A --> I get document.referrer as google
Then A to B --> I get document.referrer as A which is consistent with the behavior.
But Now If I refresh page B, then my document.referrer becomes google again.
I think I can save the last known previous URL in local storage , but that will be a anti-pattern as the browser back button can correctly take the user to the previous page (A), so somewhere the data is there already. Currently I can live with this solution as I only use this for analytics purpose on segment.com and google analytics, so refreshing will mess up my analytics numbers slightly, but still looking forward to a perfect solution so get exact data.
I can not get previous URL but with the code below I can find have a back URL or no:
typeof window !== 'undefined' && +window?.history?.state?.idx > 0
const back = async () => {
if (typeof window !== 'undefined' && +window?.history?.state?.idx > 0) {
await Router.back()
} else {
await Router.replace(fallbackURL)
}
}
A more simple way is
import { useRouter } from 'next/router';
const router = useRouter();
router.back()
When I click on a link in my /index.js, it brings me to /about.js page.
However, when I'm passing parameter name through URL (like /about?name=leangchhean) from /index.js to /about.js, I don't know how to get it in the /about.js page.
index.js
import Link from 'next/link';
export default () => (
<div>
Click{' '}
<Link href={{ pathname: 'about', query: { name: 'leangchhean' } }}>
<a>here</a>
</Link>{' '}
to read more
</div>
);
Use router-hook.
You can use the useRouter hook in any component in your application.
https://nextjs.org/docs/api-reference/next/router#userouter
pass Param
import Link from "next/link";
<Link href={{ pathname: '/search', query: { keyword: 'this way' } }}><a>path</a></Link>
Or
import Router from 'next/router'
Router.push({
pathname: '/search',
query: { keyword: 'this way' },
})
In Component
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
console.log(router.query);
...
}
Using Next.js 9 or above you can get query parameters:
With router:
import { useRouter } from 'next/router'
const Index = () => {
const router = useRouter()
const {id} = router.query
return(<div>{id}</div>)
}
With getInitialProps:
const Index = ({id}) => {
return(<div>{id}</div>)
}
Index.getInitialProps = async ({ query }) => {
const {id} = query
return {id}
}
url prop is deprecated as of Next.js version 6:
https://github.com/zeit/next.js/blob/master/errors/url-deprecated.md
To get the query parameters, use getInitialProps:
For stateless components
import Link from 'next/link'
const About = ({query}) => (
<div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
)
About.getInitialProps = ({query}) => {
return {query}
}
export default About;
For regular components
class About extends React.Component {
static getInitialProps({query}) {
return {query}
}
render() {
console.log(this.props.query) // The query is available in the props object
return <div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
}
}
The query object will be like: url.com?a=1&b=2&c=3 becomes: {a:1, b:2, c:3}
For those looking for a solution that works with static exports, try the solution listed here: https://github.com/zeit/next.js/issues/4804#issuecomment-460754433
In a nutshell, router.query works only with SSR applications, but router.asPath still works.
So can either configure the query pre-export in next.config.js with exportPathMap (not dynamic):
return {
'/': { page: '/' },
'/about': { page: '/about', query: { title: 'about-us' } }
}
}
Or use router.asPath and parse the query yourself with a library like query-string:
import { withRouter } from "next/router";
import queryString from "query-string";
export const withPageRouter = Component => {
return withRouter(({ router, ...props }) => {
router.query = queryString.parse(router.asPath.split(/\?/)[1]);
return <Component {...props} router={router} />;
});
};
Get it by using the below code in the about.js page:
// pages/about.js
import Link from 'next/link'
export default ({ url: { query: { name } } }) => (
<p>Welcome to About! { name }</p>
)
I know 2 ways to do this:
A Server-Side way, and a Client-Side way.
Method #1: SSR (Server-Side Rendering):
You should use Query Context for that page.
So use getServerSideProps instead of getStaticProps
import React from "react";
export async function getServerSideProps(context) {
const page = (parseInt(context.query.page) || 1).toString();
// Here we got the "page" query parameter from Context
// Default value is "1"
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
return {props: {products: products.results}}
// will be passed to the page component as props
}
const Page = (props) =>{
const products = props.products;
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>);
}
export default Page
The reason is that: this data cannot be pre-rendered ahead of user's request, so it must be Server-Side Rendered (SSR) on every request.
Static Pages: Use getStaticProps
Changing Content: use getServerSideProps
And here the content is changing based on query Parameters
Reference: https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props
Method #2: Next Router (Client Side):
import {useState, useEffect} from "react";
import { useRouter } from 'next/router'
const Page = () =>{
const [products, setProducts] = useState([]);
const [page, setPage] =useState((useRouter().query.page || 1).toString());
// getting the page query parameter
// Default value is equal to "1"
useEffect(()=>{
(async()=>{
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
setProducts(products.results);
// This code will be executed only once at begining of the loading of the page
// It will not be executed again unless you cahnge the page
})()
},[page]);
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default Page
Reference: https://nextjs.org/docs/api-reference/next/router
If you need to retrieve a URL query from outside a component:
import router from 'next/router'
console.log(router.query)
import { useRouter } from 'next/router';
function componentName() {
const router = useRouter();
console.log('router obj', router);
}
We can find the query object inside a router using which we can get all query string parameters.
Using {useRouter} from "next/router"; helps but sometimes you won't get the values instead u get the param name itself as value.
This issue happens when u are trying to access query params via de-structuring like:
let { categoryId = "", sellerId = "" } = router.query;
and the solution that worked for me is try to access the value directly from query object:
let categoryId = router.query['categoryId'] || '';
let sellerId = router.query['sellerId'] || '';
Post.getInitialProps = async function(context) {
const data = {}
try{
data.queryParam = queryString.parse(context.req.url.split('?')[1]);
}catch(err){
data.queryParam = queryString.parse(window.location.search);
}
return { data };
};
import { useRouter } from 'next/router'
const Home = () => {
const router = useRouter();
const {param} = router.query
return(<div>{param}</div>)
}
Also you can use getInitialProps, more details refer the below tutorial.
get params from url in nextjs
What worked for me in Nextjs 13 pages in the app directory (SSR)
Pass params and searchParams to the page:
export default function SomePage(params, searchParams) {
console.log(params);
console.log(searchParams);
return <div>Hello, Next.js!</div>;
With some builds there may be a bug that can be solved by adding:
export const dynamic='force-dynamic';
especially when deploying on Vercel.
ref: https://beta.nextjs.org/docs/api-reference/file-conventions/page#searchparams-optional
https://github.com/vercel/next.js/issues/43077