I am trying to do something very simple in a React app. I want a "Back" button to take the user to the previous page UNLESS the user has arrived from a specific page, and in that case "Back" will route them to a different page - something like:
const navigate = useNavigate();
if (lastPage === XYZ) {
navigate('/home');
} else {
navigate(-1);
}
My issue is that I can't work out how to get hold of the lastPage in react-router-dom v6 (where useHistory has been depricated).
Thanks!
From the "specific" page you should send some "state" to indicate that's where it navigated from.
Example:
Using Link component
<Link to="/newPage" state={{ fromSpecificPage: true }} />
Using Navigate component
<Navigate to="/newPage" state={{ fromSpecificPage: true }} />
Using navigate function
const navigate = useNavigate();
navigate("/newPage", { state: { fromSpecificPage: true } });
Then on the page you want to conditionally handle the back navigation check this specific route state that was possibly passed.
import { useLocation, useNavigate } from 'react-router-dom';
...
const { state } = useLocation();
const navigate = useNavigate();
...
const { fromSpecificPage } = state || {};
if (fromSpecificPage) {
navigate('/home', { replace: true });
} else {
navigate(-1);
}
I got a problem with my dynamic route. It look like this
[lang]/abc
I am trying to get query value from [lang] but when I using useRouter/withRouter i got query during 2-3 render of page ( on first i got query.lang = undefined ). its possible to get in 1 render or use any technique ?
I found something:
isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
https://nextjs.org/docs/api-reference/next/router#router-object
And the code would be like:
const router = useRouter();
useEffect(()=>{
if(!router.isReady) return;
// codes using router.query
}, [router.isReady]);
It's impossible to get the query value during the initial render.
Statically optimized pages are hydrated without the route parameters, so the query is an empty object ({}).
Next.js will populate the query after the page has been hydrated.
Next.js 10.0.5 and up
To determine if the route params are ready, you can use router.isReady inside a useEffect hook. For an example, see the answer provided by #doxylee.
Before Next.js 10.0.5
At first render of a dynamic route router.asPath and router.route are equal. Once query object is available, router.asPath reflects it.
You can rely on the query value within a useEffect hook after asPath has been changed.
const router = useRouter();
useEffect(() => {
if (router.asPath !== router.route) {
// router.query.lang is defined
}
}, [router])
GitHub Issue - Add a "ready" to Router returned by "useRouter"
In NextJS 9+, one way to ensure route parameters are immediately available for page components is to get them from the context arg passed to getServerSideProps() and pass to the component as props.
For a page like [id].js,
export function getServerSideProps(context) {
return {
props: {params: context.params}
};
}
export default ({params}) => {
const {id} = params;
return <div>You opened page with {id}</div>;
};
This is a great question and one that took a few days for me to figure out what the best approach is.
I have personally found three viable solutions to the problem of validating dynamic route path params or even just route path params in general.
The three solutions are
SSR (don't recommend) [Next >= 10]
useRouter
Middleware [Next 12 required]
In my examples a will use a route that requires a reset-token or it should be redirected.
SSR
Firstly server side rending with getServerSideProps.
Vercel recommends to use SSR as a last resort and I would highly recommend not using SSR when able (time to byte & cost).
We suggest trying Incremental Static Generation or Client-side Fetching and see if they fit your needs.
https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation
But in the case that you do, say there is some server side api validation call you require to validate the query param.
export const getServerSideProps = async (context) => {
const { token } = context.query;
if (!token) {
return {
redirect: {
permanent: false,
destination: "/",
}
}
}
return {
props: {}
// props: { token }
// You could do this either with useRouter or passing props
}
}
useRouter Secondly the easiest useRouter. When I first did this I came across the problem when nextjs/react hydrates there will be a point when the query params are null. Luckily useRouter has isReady!
import Router, { useRouter } from "next/router";
const { query, isReady } = useRouter();
useEffect(() => {
if (!isReady) return;
if (!query.token) {
Router.push("/")
}
}, [isReady])
Middleware now this is my personal favourite as it seperates the functionality in a clean way imo.
I found this based of a vercel example. I would highly recommend reading through a bunch of these to find best practices.
https://github.com/vercel/examples/
import { NextResponse, NextRequest } from 'next/server'
export async function middleware(req) {
const { pathname, searchParams } = req.nextUrl
if (pathname == '/reset-token') {
const index = searchParams.findIndex(x => x.key === "token")
// You could also add token validation here.
if (!index) {
return NextResponse.redirect('/')
}
}
return NextResponse.next()
}
Here is the repo which has some cool filtering of query parameters.
This is a more soft approach instead of hard redirecting.
https://github.com/vercel/examples/tree/main/edge-functions/query-params-filter
Nico also has a great answer on this, expect I wouldn't recommend using hooks like in his example, instead use isReady.
https://stackoverflow.com/a/58182678/4918639
For Class Component Lovers
The even better approach is to listen for a dedicated event for this routeChangeComplete using this.props.router.events.on method, inside componentDidMount if you're using class component -
routeChangeComplete = () => {
// this WILL have valid query data not empty {}
console.log(this.props.router.query);
};
componentDidMount() {
this.props.router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
this.props.router.events.off("routeChangeComplete", this.routeChangeComplete);
}
Ref: https://nextjs.org/docs/api-reference/next/router#routerevents
routeChangeComplete: Fires when a route changed completely.
Practically when isReady has become true or when router.query object has data.
For NextJS version - 12.0.8
"If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps."
=async functions
refference:https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops
Simply putting that async function on the page notifies NextJS of its presence.During prerendering stage of the component, the query object of the router will be empty.
isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
refference: https://nextjs.org/docs/api-reference/next/router
solution:
import { useRouter } from 'next/router';
const Fn = () =>{
const router = useRouter();
const { param } = router.query;
const fetchData = async () => {
await fetch();
}
useEffect(() => {
fetchCat();
}, [router.isReady]);
}
I resolved my problem that I need it in Hoc component.
I wrapped using withRouter(withLocale(Comp)) and create conditional in HOC
export default function withLocale(WrappedPage) {
const WithLocale = ({ router, ...props }) => {
const { lang } = router.query;
if (!lang || !isLocale(lang)) {
return <Error statusCode={404} />;
}
return (
<LocaleProvider lang={lang}>
<WrappedPage {...props} />
</LocaleProvider>
);
};
return WithLocale;
}
Next.js <= 10.0.5
This is a good work around, I found around from this comment
Add useQuery.ts helper file
// useQuery.js
import { useRouter } from 'next/router';
// Resolves query or returns null
export default function useQuery() {
const router = useRouter();
const ready = router.asPath !== router.route;
if (!ready) return null;
return router.query;
}
usage
// In your components (instead of useRouter)
const query = useQuery();
useEffect(() => {
if (!query) {
return;
}
console.log('my query exists!!', query);
}, [query]);
Class Component | 12/16/2022 | React JS 18.2.0 | Next JS 13.0.6
I got the answer for those who want to use Class Component. This was actually nowhere to be found ! Enjoy !
You will add if(this.props.router.isReady) and include return in the condition in render().
.
.
import { withRouter } from 'next/router';
import { Component } from 'react';
class User extends Component {
...
render() {
if(this.props.router.isReady){ // Add this condition and include return ()
// Do anything
console.log(this.props.router.query) // Log 'query' on first render
return (
<div>
<SearchBar pid={this.props.router.query.pid} /> // Pass the query params to another component if needed
</div>
);
}
}
}
export default withRouter(User);
I am struggling with getting my NavBar to not display/show until a user has logged in (received a token). I know you can set it up using a ternary but I am not able to get one to function. If another option besides a ternary works I am okay with that.
import Auth from './Auth/Auth';
import Sitebar from './Home/Navbar';
import ReviewIndex from './Reviews/ReviewIndex';
import Navigation from './Home/Navigation'
import {
BrowserRouter as Router
} from 'react-router-dom';
function App() {
const [sessionToken, setSessionToken] = useState('');
useEffect(() => {
if (localStorage.getItem('token')){
setSessionToken(localStorage.getItem('token'));
}
}, [])
const updateToken = (newToken) => {
localStorage.setItem('token', newToken);
setSessionToken(newToken);
console.log(sessionToken)
}
const clearToken = () => {
localStorage.clear();
setSessionToken('');
window.location.href="/"
}
const protectedViews = () => {
return (sessionToken === localStorage.getItem('token') ? <ReviewIndex token={sessionToken}/>: <Auth updateToken={updateToken}/>)
}
return (
<div className="App">
<Router>
<Sitebar sessionToken={sessionToken} clickLogout={clearToken}/>
<Navigation sessionToken={sessionToken} />
{protectedViews()}
</Router>
</div>
);
}
export default App;
the way i would approach this is i would use the turnary operator to conditionally render it. so here you are setting the session token as soon as the component renders and you are only doing it once.
so heres what you could write to conditionally render this.
{sessionToken ? "Put Jsx here that you want to render if they are authenticated" : "Put Jsx here to render if they are not authenticated"}
// css file
.navigation {
visibility: 'hidden';
// or
display: 'none';
}
// component
<Navigation className={sessionToken ? 'navigation': ''} />
It really depends what you want to do. lizardcoder's solution won't mount the component at all until the user is logged in. My solution will hide it, but it will mount. If you don't want to initialize anything in Navigation until the user is logged in, lizardcoder's is the right way to go. If you just want to hide it, css is a good way to go.
I have a small issue. I'm very very very new to Nextjs and I'm trying to learn by making a app. I have managed to make a Login system using next and I have few issues when securing routes. I have successfully added a cookie after successful login. Now I want to validate the cookie whenever user go to a protected route. I have followed below steps using this tutorial.
Made a Higher order component and checked the cookie validation using it.
Wrap the protected component using it.
Below is my HOD.
import { useRouter } from "next/router";
import Cookies from 'js-cookie';
const withAuth = (WrappedComponent) => {
return (props) => {
if (typeof window !== "undefined") {
const Router = useRouter();
const accessToken = Cookies.get('token');
if (!accessToken) {
Router.replace("/");
return null;
}
return <WrappedComponent {...props} />;
}
return null;
};
};
export default withAuth;
And then I have wrapped my component using above HOD.
import React, { Component } from 'react';
import withAuth from '../utils/withAuth';
class Home extends Component {
render() {
return (
<div>
HOME
</div>
);
}
}
export default withAuth(Home);
ISSUE #1
Above HOD is showing a console warning saying below.
Warning: Expected server HTML to contain a matching in .
div
Is their anyway I can fix this issue? As per some github answer I have found this can be solved using useEffect. SOURCE
Can anyone help me with this?
ISSUE #2
In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
After spending some time. I was able to fix the issue by using below code. Now I just want to know the answer for 2nd issue mentioned above.
ISSUE #2 In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
Code I use to fix the issue
import Router from 'next/router'
import Cookies from 'js-cookie';
import React, { useState, useEffect } from 'react';
const withAuth = (WrappedComponent) => {
return (props) => {
const [isLoggedIn, setLoginStatus] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
const accessToken = Cookies.get('token');
if (accessToken) {
setLoginStatus(true)
}
else {
Router.push("/")
}
}
}, []);
if (isLoggedIn) {
return <WrappedComponent {...props} />;
} else {
return null;
}
}
};
export default withAuth;
I have lots of static forms which i show the user when he clicks on the main menu and goes to a specific route, what i want to do now is to check when going to a route if that component has permission to be visited, i can do this by doing a simple post to server but i am confused and i don't know where should be the place to do this check or post.
Here are some of the solutions i thought of:
1- Writing a Higher order component and wrapping each static component with it
2- creating a base class and making each static form to inherit it while doing this check in the parent class
3- Or maybe using the routes as a solution since i am using the react-router ?
I will appreciate any help or tips.
Thanks.
Create a custom hook like so:-
const useAdmin = (url:string) => {
const [admin, setAdmin] = React.useState(false);
React.useEffect(() => {
post(url, {some: body}).then(res => {
setAdmin(res.admin);
}).catch(err => {
setAdmin(false);
});
}, [])
return [admin];
}
Then use it anywhere:-
const mycomponent = props => {
const [admin] = useAdmin('https://foobar.com/checkPermission');
//Rest of the logic according to the variable 'admin'
return (
<div>
{
admin? <div/>:null
}
</div>
)
}
Or think of admin as permission. Pass it some url for different routes and it will handle it.
I do something similar using react-router as well. I made my own route component that wraps around react-router's route that checks permissions and conditionally renders the route or redirects.
If you're doing the api call each time, then it would look something close to this.
class AppRoute extends Component {
state = {
validCredentials: null
}
componentDidMount() {
// api call here + response
if (ok) {
this.setState({validCredentials: true})
} else {
this.setState({ validCredentials: false})
}
}
render() {
const { validCredentials } = this.state
if (validCredentials) {
return <Route {...this.props} />
} else if (validCredentials === false) {
return <Redirect to="somewhere"/>
}
return null
}
}
You can definitely accomplish this using a Higher Order Component. Just set a state for the user on login like "admin" or "basic_user." According to this state some buttons or forms are going to be available for the user to access. You can also save these access permissions to your backend and call it in the HOC whenever the user logs in.