In parent
import LoginPage from "pages/admin";
export function Home() {
return <LoginPage />;
}
In child
import { useRouter } from "next/router";
export default function LoginPage() {
const router = useRouter();
return (router.replace("/users");)
I got JSX error.
Because child is not used JSX.
But in react-router-dom, There is <Navigate>.
So in that case, it can fix.
Is there <Navigate> as react-router-dom in Next.js?
You can use router.push for redirections:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Here you would fetch and return the user
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])
return <p>Redirecting...</p>
}
Next documentation: https://nextjs.org/docs/api-reference/next/router
Custom JSX component
If you need a component to do this:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
function Redirect({ to }) {
const router = useRouter()
useEffect(() => {
router.push(to)
}, [])
return null
}
Then just use it as a react-router <Redirect to="" />
Related
in Next JS < 13 we had router.events and in the _app.js we could add an effect like
import { useRouter } from 'next/router'
import NProgress from 'nprogress'
const Main = ({ Component, pageProps }) => {
const router = useRouter()
useEffect(function nprogressOnRouteChange() {
router.events.on('routeChangeStart', NProgress.start)
router.events.on('routeChangeError', NProgress.done)
router.events.on('routeChangeComplete', NProgress.done)
return () => {
router.events.off('routeChangeStart', NProgress.start)
router.events.off('routeChangeError', NProgress.done)
router.events.off('routeChangeComplete', NProgress.done)
NProgress.remove()
}
}, [])
...
}
but now the useRouter has been moved to next/navigation, and the hook returned object
no longer has any event property in it, am I missing anything?
tried importing the Router from next/router in the new layout.tsx file but the events are not working there either
import Router from 'next/router'
export default function Transition({ children }) {
useEffect(() => {
Router.events.on('routeChangeStart', () => {
console.log('start')
})
}, [])
...
}
I am totally a beginner in React and while practising I ran into this issue. Through searching, I found out that 'withRouter' is not supported anymore by 'react-router-dom v6'. But I can't figure out how to change my code compatibly to v6. Does anyone know how to change this code instead of using 'withRouter'? Thanks in advance!
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { readPost, unloadPost } from '../../modules/post';
import PostViewer from '../../components/post/PostViewer';
const PostViewerContainer = ({ match }) => {
// 처음 마운트될 때 포스트 읽기 API요청
const { postId } = match.params;
const dispatch = useDispatch();
const { post, error, loading } = useSelector(({ post, loading }) => ({
post: post.post,
error: post.error,
loading: loading['post/READ_POST']
}));
useEffect(() => {
dispatch(readPost(postId));
// 언마운트될 때 리덕스에서 포스트 데이터 없애기
return () => {
dispatch(unloadPost());
};
}, [dispatch, postId]);
return <PostViewer post={post} loading={loading} error={error} />;
};
export default withRouter(PostViewerContainer);
enter image description here
That is correct, the withRouter Higher Order Component (HOC) was removed in react-router-dom#6.
Since PostViewerContainer is a function component, just use the React hooks directly. There's no need really for the withRouter HOC. In this case it's the useParams hook you need to import and use.
Example:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom'; // <-- import useParams hook
import { readPost, unloadPost } from '../../modules/post';
import PostViewer from '../../components/post/PostViewer';
const PostViewerContainer = () => { // <-- remove match prop
// 처음 마운트될 때 포스트 읽기 API요청
const { postId } = useParams(); // <-- call hook and destructure param
const dispatch = useDispatch();
const { post, error, loading } = useSelector(({ post, loading }) => ({
post: post.post,
error: post.error,
loading: loading['post/READ_POST']
}));
useEffect(() => {
dispatch(readPost(postId));
// 언마운트될 때 리덕스에서 포스트 데이터 없애기
return () => {
dispatch(unloadPost());
};
}, [dispatch, postId]);
return <PostViewer post={post} loading={loading} error={error} />;
};
For reference, if you needed to still use an HOC for class based components you'd need to either convert them to function components or create a custom withRouter HOC.
Example:
import { useLocation, useNavigate, useParams } from 'react-router-dom';
const withRouter = Component => props => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return (
<Component
{...props}
location={location}
navigate={navigate}
params={params}
/>
);
};
export default withRouter;
Iam trying to display a component whenever response of an api is true. but if i try to do it in the axios where iam sending api request it does not work and if i remove the below return it gives me an error that there is nothing to render.
My code
import React from "react";
import { useState, useEffect } from "react";
import { SignUpComponent } from "../index";
import axios from "axios";
import { Redirect, useParams } from "react-router-dom";
import { getToken } from "../../common/constants/variables";
function Reference() {
const [openSignUp, setOpenSignup] = useState(true);
const [refer, setRef] = useState({});
let { ref } = useParams();
function toggleToSignUp() {
setTimeout(() => {
setOpenSignup(true);
}, 350);
}
axios
.post("https://theappsouk.com/api/v1/check-referral", {
ref: ref,
})
.then((response) => {
if (response.data.status == true) {
return (
<SignUpComponent open={openSignUp} toggleModal={toggleToSignUp} />
);
} else{
<Redirect to= '/'/>
console.log("NOTHING")
}
console.log("REFFEERR", JSON.stringify(response.data.status));
});
console.log("REFF", JSON.stringify(ref));
return ( //what ever the api response is it seems to render only this return statement
<div>
<SignUpComponent open={openSignUp} toggleModal={toggleToSignUp} />
</div>
);
}
export default Reference;
you should request axios in the useEffect, and display all the UI inside the return
import React from "react";
import { useState, useEffect } from "react";
import { SignUpComponent } from "../index";
import axios from "axios";
import { Redirect, useParams } from "react-router-dom";
import { getToken } from "../../common/constants/variables";
function Reference() {
const [openSignUp, setOpenSignup] = useState(true);
const [status, setStatus] = useState(true);
const [refer, setRef] = useState({});
let { ref } = useParams();
function toggleToSignUp() {
setTimeout(() => {
setOpenSignup(true);
}, 350);
}
useEffect(() => {
axios
.post("https://theappsouk.com/api/v1/check-referral", {
ref: ref,
})
.then((response) => {
setStatus(response.data.status)
});
}, []);
return ( //what ever the api response is it seems to render only this return statement
<div>
{
status? <SignUpComponent open={openSignUp} toggleModal={toggleToSignUp} /> :
<Redirect to= '/'/>
}
</div>
);
}
export default Reference;
So I'm creating a loader component that requests data to a server, then based on the response - then the loader page redirects/reroutes them to other pages.
But for some reason history.push changes the route but does not render the component and remains with the <LoaderPage> component. Not sure what am I missing. So kindly help.
I have wrapped all pages with the <LoaderPage> component as my goal is when every time a user visits any route the <LoaderPage> component renders first then it does its job then redirects/reroutes users to other pages.
loader.tsx
import React from 'react';
import { useHistory } from 'react-router-dom'
import { AnimatedLodingScreen } from './loaders/AnimatedLodingScreen'
type loaderProps = {
children: React.ReactNode;
};
export const LoaderPage = ({ children }:loaderProps) => {
const history = useHistory();
React.useEffect(() => {
const session = http.get('/some-route');
session.then((result) => {
if(result.authenticated) {
history.push('home'); // when user is logged
}
history.push('/login'); // not authenticated
}
}, [])
return (
<AnimatedLodingScreen/>
);
}
app.tsx
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { observer } from 'mobx-react';
import { LoaderPage } from 'pages/loaders/LoaderPage';
import { SomeComponent1, SomeComponent2 } from 'pages/index'
export const App: React.FC = observer(() => {
return (
<BrowserRouter>
<LoaderPage>
<Route path='/home' exact component={SomeComponent1}/>
<Route path='/login' exact component={SomeComponent2}/>
// and so on... I have alot of routes in fact these routes are looped via .map and
// type-checked i just put it like this for simplicity
</LoaderPage>
</BrowserRouter>
);
});
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from 'app/app';
ReactDOM.render(<App/>, document.getElementById('root'));
The children props taken by your LoaderPage component isn't used for render anywhere within it, and thus nothing is rendered.
export const LoaderPage = ({ children }:loaderProps) => {
const history = useHistory();
React.useEffect(() => {
const session = http.get('/some-route');
session.then((result) => {
if(result.authenticated) {
history.push('home'); // when user is logged
}
history.push('/login'); // not authenticated
}
}, [])
return (
{children || <AnimatedLodingScreen/>}
);
}
You can use a state to save whether the data loading is completed or not and render children based on that
export const LoaderPage = ({ children }:loaderProps) => {
const history = useHistory();
const [isLoaded, setLoaded] = React.useState(false)
const [redirectPath, setRedirectPath] = React.useState('')
React.useEffect(() => {
const session = http.get('/some-route');
session.then((result) => {
if(result.authenticated) {
return setRedirectPath('/home') // when user is logged
}
setRedirectPath('/login') // not authenticated
}
}, [])
function redirectToPath() {
setLoaded(true);
history.push(redirectPath)
}
if(isLoaded) { return <>{children}</> }
return <AnimatedLodingScreen onAnimationEnd={redirectToPath} /> // onAnimationEnd is the function passed as prop to the component that should be invoked on animation ends
}
GraphQL queries in my components are not running on dynamic routes when I try to access the query string with router.query.xxx.
I have the following file
// ./pages/section/[slug].js
import { useRouter } from 'next/router';
import AppLayout from '../../components/styles/_AppLayout';
const Section = () => {
const router = useRouter();
return <AppLayout>Hi</AppLayout>;
};
export default Section;
The page displays fine, but as soon as I add {router.query.slug} and refresh the page, it gives me a TypeError because the GraphQL queries do not run. As you can see in the image below, me.firstName is undefined because the GraphQL query did not run
This is the code in _AppLayout.js
import styled from 'styled-components';
import Navigation from '../Navigation';
const Wrapper = styled.div`...`;
const AppLayout = props => {
return (
<Wrapper>
<Navigation />
<main>{props.children}</main>
</Wrapper>
);
};
export default AppLayout;
Any ideas why this might be happening and how to fix it?
Thanks
I was able to solve my issue two ways
Using withRouter
import { withRouter } from 'next/router';
import TestComponent from '../../components/TestComponent';
import AppLayout from '../../components/styles/_AppLayout';
const Section = props => {
return <AppLayout>Hi {props.query.slug}</AppLayout>;
};
export default withRouter(Section);
and passing the query parameter as props via getInitialProps
const Section = ({slug}) => {
return <AppLayout>Hi {slug}</AppLayout>;
};
Section.getInitialProps = async ({ query }) => {
const { slug } = query;
return { slug };
};
export default Section;
The following method worked for me, I am using React Hooks with Context and I need to also use the nextJS route with it, so following configuration can be followed.
Note: If you are using GraphQL then that can be also wrapped around the final JSX in _app.js
_app.js:
import { withRouter } from "next/router";
BuilderProvider is here Context Provider
const InjectRouterContext = withRouter(({ router, children }) => {
return <BuilderProvider value={router}>{children}</BuilderProvider>;
});
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<InjectRouterContext>
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
</InjectRouterContext>
);
}
}
Now in the Page, here it is somepage.js:
import { useRouter } from "next/router";
const somepage = () => {
const router = useRouter();
const { id } = router.query;
return (//JSX Here);
}
somepage.getInitialProps = async ({ query }) => {
const { slug } = query;
return { slug };
};
export default somepage;