So I've got React-Router set up as follows:
// App.js
render(){
console.log(this.props)
return (
<div>
<Header />
{this.props.children}
<Footer />
</div>
)
}
// routes.js
export default (
<Router>
<Route path="/" component={App} >
<IndexRoute component={HomePage} />
<Route path="/destination" component={DestinationIndexHandler} />
<Route path="/destination/:slug" component={DestinationHandler} />
<Route path="/destination/:slug/:article" component={ArticleHandler} />
<Route path="/article" component={ArticleIndexHandler} />
<Route path="/article/:slug" component={ArticleHandler} />
<Route name='contact' path="/contact" component={ContactHandler} />
<Route path="*" component={NotFoundHandler}/>
</Route>
</Router>
);
And finally:
// server.js
app.get(/^(?!.*(api|.js|.css)).*$/, function (req, res) {
let location = createLocation(req.url);
console.log("LOCAL")
match({ routes, location }, (error, redirectLocation, renderProps) => {
console.log(location);
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
let content = ReactDOMServer.renderToString(<RoutingContext {...renderProps} />);
console.log(content);
res.status(200).render('index', {
content: content,
staticPort: DEV_PORT
});
} else {
res.status(404).send('Not found')
}
})
});
For some reason, when I refresh the page, the server side renders the view successfully but the browser renders the home view (IndexRoute). If you remove the IndexRoute, it only renders the header and footer.
If I log in the terminal I get 3 returns, the first and second being:
CHILDREN { '$$typeof': Symbol(react.element),
type: [Function: ContactHandler],
and finally a call to NotFoundHandler, even though the route is there. Any ideas?
Related
This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 8 months ago.
I am using react-router-dom#6.3.0
I have created a React app where certain Private pages are accessible only users who have logged in.
You can find a demo here, and a GitHub repo here.
A simplified version of this is shown below.
I have wrapped every Private page in its own RequireLogin component, and this works:
<Route
path="/private1"
element={
<RequireLogin redirectTo="/">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
The RequireLogin component redirects to a page with the Login component if the user is not logged in, and renders the requested component only to a logged in user.
My question is this:
Is it there a way to wrap all the Private routes inside one RequireLogin component, or do I have to wrap each one separately?
import React, { createContext, useContext, useState } from 'react'
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
NavLink
} from "react-router-dom";
const UserContext = createContext()
const UserProvider = ({children}) => {
const [ loggedIn, logIn ] = useState("")
return (
<UserContext.Provider
value={{
loggedIn,
logIn
}}
>
{children}
</UserContext.Provider>
)
}
function App() {
return (
<Router>
<UserProvider>
<Routes>
<Route
path="/"
element={<NavLink to="/login">Log In</NavLink>}
/>
<Route
path="/login"
element={<Login />}
/>
<Route
path="/private1"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
<Route
path="/private2"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #2"
/>
</RequireLogin >
}
/>
</Routes>
</UserProvider>
</Router>
);
}
function Menu({hideLogOut}) {
const { loggedIn } = useContext(UserContext)
if (loggedIn) {
if (!hideLogOut) {
return <ul>
<li><NavLink to="/login">Log Out</NavLink></li>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
} else {
return <ul>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
}
} else {
return <p>Not Logged In</p>
}
}
function RequireLogin ({ children, redirectTo }) {
const { loggedIn } = useContext(UserContext);
return loggedIn
? children
: <Navigate to={redirectTo} />;
}
function Private({text}) {
return (
<div>
<Menu />
<h1>{text}</h1>
</div>
)
}
function Login() {
const { loggedIn, logIn } = useContext(UserContext)
const toggleLogged = () => {
logIn(!loggedIn)
}
return (<div>
<Menu
hideLogOut={true}
/>
<label htmlFor="loggedIn">
<input
type="checkbox"
name="loggedIn"
id="loggedIn"
checked={loggedIn}
onChange={toggleLogged}
/>
Pretend that we are logged in
</label>
</div>)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
I use a second router for the private routes, wrapped with a single <RequireLogin>. Example:
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegistrationPage />} />
<Route path="*" element={
<RequireLogin>
<Routes>
<Route path="/" element={<FeedPage />} />
<Route path="/explore" element={<ExplorePage />} />
<Route path="/user/:username" element={<UserPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</RequireLogin>
} />
</Routes>
I am creating a dashboard using nested routes. But when I tries clicking a Link, the URL on the browser does change but the useLocation hook doesn't get update so my useEffect hook doesn't get fired to reload data.
This is my App main routes:
function App() {
return (
<Router>
<Header />
<Switch>
<Route path="/login" component={ Login } />
<ProrectedRoute exact path="/" component={ Home } />
<ProrectedRoute path="/blogs/:title" component={ BlogPage } />
<ProrectedRoute path="/new" component={ NewBlog } />
<ProrectedRoute path="/dashboard/:part" component={ UserDashboard } />
<ProrectedRoute path="/:name" component={ ProfilePage } />
</Switch>
<Footer />
</Router>
);
}
And this is my dashboard:
export const UserDashboard = () => {
const dispatch = useDispatch();
const location = useLocation();
useEffect(() => {
switch(location) {
case '/dashboard/posts':
dispatch(getOwnPosts());
break;
case '/dashboard/bookmarks':
dispatch(getBookmarkPosts());
break;
case '/dashboard/followings':
break;
default:
break;
};
}, [location]);
return (
<div className="dashboard">
<Router>
<div className="dashboard__sidebar">
<Link to="/dashboard/posts">Your posts</Link>
<Link to="/dashboard/bookmarks">Your bookmarks</Link>
<Link to="/dashboard/followings">Your followings</Link>
</div>
<div className="dashboard__main">
<Switch>
<Route exact path="/dashboard/posts">
<BlogRecommendationList selector={selectUserPosts} />
</Route>
<Route exact path="/dashboard/bookmarks">
<BlogRecommendationList selector={selectUserBookmarkedPosts} />
</Route>
</Switch>
</div>
</Router>
</div>
)
}
I have found the error. It is because I use 2 Router in the same application (1 in the App.js and 1 in Dashboard.js). There should be only 1 router in an application. Remove Router from dashboard makes it work.
I have two main pages: LoginPage and HomePage. HomePage is main page with navbar and content. I'm trying to implement nested routing.
loginPage
HomePage
SchoolsPage
UserDetails
I prepared routing for them:
<Switch>
<ProtectedRoute
key="loginPage"
path={`${ROOT_PATHS.login}`}
component={AuthPageContainer}
shouldBeRedirected={authState === 'AUTHENTICATED'}
authenticationPath={`${ROOT_PATHS.version}`}
/>
<ProtectedRoute
key="homePage"
path={ROOT_PATHS.version}
component={HomePageContainer}
shouldBeRedirected={authState === 'NOT_AUTHENTICATED'}
authenticationPath={`${ROOT_PATHS.login}`}
/>
</Switch>
Home page:
export const HomePage: React.FC<HomePageProps> = ({ fetchUserInfo, user, isLoading, userLogout }): JSX.Element => {
useEffect(() => {
fetchUserInfo()
}, [fetchUserInfo])
return (
<LoadingContent isLoading={isLoading}>
<Navbars user={user} userLogout={userLogout} />
<Pages />
</LoadingContent>
)
}
An routings in Pages:
export const Pages: React.FC = (): JSX.Element => {
const { url, path } = useRouteMatch()
return (
<Switch>
<Route path={`${url}${ROOT_PATHS.userDetails}`} exact component={UserDetailsContainer} />
<Route path={`${url}${ROOT_PATHS.schools}`} exact component={SchoolListContainer} />
<Route path={`${path}`} exact>
{console.log('redirected')}
<Redirect to={`${url}${ROOT_PATHS.schools}`} />
</Route>
</Switch>
)
}
And paths:
export const ROOT_PATHS = {
login: '/login',
version: '/v1',
schools: '/schools',
userDetails: '/userDetails'
}
My index.tsx:
// axios should be configured before saga because saga makes requests on startup
configureAxios()
const store = configureStore()
export const DOMStructure: React.FC = (): JSX.Element => (
<Suspense fallback={<Spinner animation="border" role="status" />}>
<Provider store={store}>
<ConnectedRouter history={history}>
<CookiesProvider>
<AppContainer />
</CookiesProvider>
</ConnectedRouter>
</Provider>
</Suspense>
)
ReactDOM.render(<DOMStructure />, document.getElementById('root'))
It all works fine when navigating via links in navbar.
But when I refresh page, it never matches to any child-routes and is redirected to v1/schools (I checked that with console.log('redirected').
So I cannot render page under specific ur. When I put 'localhost:3000/v1/userDetails' in browser, it's always redirected to 'localhost:3000/v1/schools'.
Why is that? What i'm missing?
I am trying to go to page and pass id, but my ownParams in mapStatetoProps remains empty. Do note the url generated is correct.
My current structure is
index.jsx
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
app.jsx
class App extends Component {
render() {
return (
<div>
<Nav />
<Router />
</div>
);
}
}
export default App;
router.jsx
const Router = () => (
<router>
<Switch>
<Route path='/signup' component={Signup} />
<Route path='/browse' component={Browse}/>
<Route path='/detail/:id' component={Detail} />
</Switch>
</router>
)
export default Router;
detail.jsx
export default class App extends Component {
render() {
return (
<div>
<Info />
<Message/>
</div>
);
}
}
Now in info.jsx at the end of the file
function mapStateToProps({posts}, ownProps){
console.log('>>>>>>>> post_show State to props', ownProps);
return { post : posts[ownProps.match.params.id]};
}
export default connect(mapStateToProps, { fetchPost, deletePost })(PostsShow);
this gives me an error because ownProps is empty.
When I use the example of pinterest , the modal, I found that when the page is scrolled, and opening modal, and the background of page can not be the same as prev , modal background after opening the scroll is zero
<Router history={newHistory}>
<Route path="/" component={App}>
<Route path="home" component={Container} >
<IndexRedirect to="home"/>
<Route path="login" component={Authentication}/>
<Route path="home" component={Home}/>
<Route path="post/:id" component={Post}/>
</Route>
</Route>
</Router>
<Link to='/post/123' state={{isModal:true}}/>
<Link to='/home' state={{isModal:false}}/>
<Link to='/login' state={{isModal:true}}/>
Container = React.createClass({
render() {
let isModal = (location.state && location.state.modal);
return (
<div id="MeContainer">
<ModalControlOpen isModal={isModal}>
<Modal isOpen={true} returnTo={location.state.returnTo}>
{this.props.children ? childrenWithData : <h1>咦?内容呢?</h1>}
</Modal>
</ModalControlOpen>
<ModalControlClose isModal={isModal}>
{childrenWithData}
</ModalControlClose>
</div>
)
}
});
ModalControlOpen = React.createClass({
render(){
if (this.props.isModal) {
return (
this.props.children
)
} else return <div></div>
}
});
ModalControlClose = React.createClass({
shouldComponentUpdate(nextProp){
return !nextProp.isModal
},
render(){
return (
this.props.children
)
}
});