React-router: `<Link to=...>` does't trigger navigation change - reactjs

I've got code similar to following:
var browserHistory = ReactRouter.browserHistory;
var Router = ReactRouter.Router;
var Route = ReactRouter.Route;
var Link = ReactRouter.Link;
class App extends React.Component {
render() {
return (
<div>
<h1>Here is content:</h1>
{this.props.children}
<Link to="/Welcome">Welcome</Link> |
<Link to="/Login">Login</Link>
REFERENCE LINK
</div>
);
}
}
class Welcome extends React.Component {
render() {
return (
<div>
<h1>No hejaaaa - welcome</h1>
</div>
);
}
}
class Login extends React.Component {
render() {
return (
<div>
<h1>No hejaaaa - Login</h1>
</div>
);
}
}
const Routes = (
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="Welcome" component={Welcome}/>
<Route path="Login" component={Login}/>
<Route path="*" component={Welcome}/>
</Route>
</Router>
);
// init file:
var RouterContext = ReactRouter.RouterContext
var match = ReactRouter.match
match({
routes: Routes,
location: document.location.pathname
}, (err, redirectLocation, renderProps) => {
ReactDOM.render(<RouterContext {...renderProps} />, document.querySelector('#app'));
});
Markup is generated correctly, but the problem is: Clicking in Links doesn't work at all.
I am doing something wrong?
My libs:
"react": "0.14.7",
"react-dom": "0.14.7",
"react-router": "2.0.0"
JSFIDDLE: https://jsfiddle.net/Lp3gzott/ (same code but babelified)

I found the solution in react-router documentation. According to Server Rendering Guide:
In back end set:
match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
renderToString(<RouterContext {...renderProps} />
})
Notice RouterContext instead of Router and lack of history field in match params
In front-end:
match({ history, routes }, (error, redirectLocation, renderProps) => {
ReactDOM.render(<Router {...renderProps} />, mountNode)
})
Notice lack of location param for match
In routes file:
export <Route instead of <Router
Error React attempted to reuse markup in a container but the checksum was invalid. is not showing up again.
Links are working like a charm!

match() is a Server side rendering construct, its intentional static, because on the server you only ever respond to a single route at a time. On the client you want to actually render a Router component
ReactDOM.render((
<Router>
{ Routes }
</Router>
), document.querySelector('#app'))
Your, markup mismatch is probably due to a different issue, and you might want to check out one of the many "universal react" starters.

Related

Integrating an optional "App Shell" into React Router [duplicate]

I am having trouble writing code to render a login page with no navbar and sidebar. I have come across some pages that ask similar questions but none seem to pertain to my current situation.
How to hide navbar in login page in react router
the example given is great but I believe the way of accomplishing that same task has changed with react-router-dom v6 leading me to read about this change in https://dev.to/iamandrewluca/private-route-in-react-router-v6-lg5
It seems I am not understanding a certain aspect about routing with React Router. In the code below I have two Routes. One of the routes(Login) I would like to have render without the NavBar and SideBar component.
const App = () => {
return (
<>
<Routes>
<Route path="/login" element={<LoginPage />} />
</Routes>
<NavBar />
<SideBar />
<main className={styles["main--container"]}>
<div className={styles["main--content"]}>
<Routes>
<Route path="/" element={<Dashboard />} />
</Routes>
</div>
</main>
</>
);
};
An alternative, that I also tried, would be to move the NavBar and SideBar tags into the Dashboard component, but then I would essentially have to do the same copy and paste for any new components. This method felt wrong and inefficient , but if this is the correct way of doing it I will do the needful
Edit: I think it's important to include what it currently does is load the Login page with the NavBar and SideBar included. Navigating to the dashboard component has the NavBar and SideBar but this is intended.
What I would like is for the Login page not to have the NavBar and SideBar
If I understand your question, you are wanting to render the nav and sidebar on the non-login route. For this you can create a layout component that renders them and an outlet for the nested routes.
Using nested routes
import { Outlet } from 'react-router-dom';
const AppLayout = () => (
<>
<NavBar />
<SideBar />
<main className={styles["main--container"]}>
<div className={styles["main--content"]}>
<Outlet /> // <-- nested routes rendered here
</div>
</main>
</>
);
const App = () => {
return (
<>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route element={<AppLayout />} >
<Route path="/" element={<Dashboard />} /> // <-- nested routes
</Route>
</Routes>
</>
);
};
Using a routes configuration and useRoutes hook
const routesConfig = [
{
path: "/login",
element: <LoginPage />,
},
{
element: <AppLayout />,
children: [
{
path: "/",
element: <Dashboard />,
},
],
},
];
...
import { useRoutes } from 'react-router-dom';
const App = () => {
const routes = useRoutes(routesConfig);
return routes;
};
Using a routes configuration and data routers (introduced in v6.4.0)
const routesConfig = [
{
path: "/login",
element: <LoginPage />,
},
{
element: <AppLayout />,
children: [
{
path: "/",
element: <Dashboard />,
},
],
},
];
...
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter(routesConfig);
const App = () => {
return <RouterProvider router={router} />;
};
The easiest way for you to hide the navbar would be to go to the login page component and call useLocation(). Then you woulf do something like this after declaring the use location. And assigning it to a variable location
{ location.pathname === "/login" ? null : (
Render the whole navbar component);
Not sute if you can be able to read as I type from my phone

Animated react routes not working with <Switch> component

I am trying to build a simple router for my React application, which would consist of a few routes generated from an array of objects and a static 404 route. The requirement is that transitions between every route must be animated.
I am using react-router for the browser and react-transition-group.
I want to achieve something like this (stripped-down, incomplete pseudo-code):
const routes = [
{ path: "/", component: Home },
{ path: "/about", component: About },
{ path: "/contact", component: Contact }
];
const createRoute = (route) => {
return (
<CSSTransition className="page" timeout={300}>
<Route path={route.path} exact component={route.component} />
</CSSTransition>
);
}
<Router>
{routes.map(createRoute)}
<CSSTransition className="page" timeout={300}>
<Route component={PageNotFound} />
</CSSTransition>
</Router>
A full version of this code can be found on this Codesandbox:
https://codesandbox.io/s/react-router-switch-csstransition-catch-all-route-bug-forked-qzt9g
I need to use the <Switch> component to prevent the 404 route from showing in all the other routes of the application, but as soon as I add the <Switch>, the animations stop working.
In the example above, you will see that the animations don't work when used with <Switch>, despite following the guides from official docs of both react-router and react-transition-group.
However, they work perfectly without the use of the <Switch>, but then of course I end up with 404 route showing all the time.
Expected result:
animated transitions between all routes, those dynamically created as well as the static 404 page
Actual result:
no animations at all or animations with 404 route always showing
I have spent the entire day trying to find a solution to the problem that I encountered. Unfortunately I can't seem to find anything that would remotely help me fix the issue I'm facing, and I've searched on Google, Stack Overflow, Medium and finally I'm back here hoping someone can help me out please.
In order to have the animation working with the Switch component, you have to pass the right location with withRouter to the CSSTransition, coupled with TransitionGroup component.
I've modified you sandbox code with the following working solution:
import React from "react";
import ReactDOM from "react-dom";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
withRouter
} from "react-router-dom";
import "./styles.css";
const rootElement = document.getElementById("root");
const components = {
Home: () => <div>Home page</div>,
About: () => <div>About the company</div>,
Contact: () => <div>Contact us</div>,
NotFound: () => <div>404</div>,
Menu: ({ links, setIsSwitch }) => (
<div className="menu">
{links.map(({ path, component }, key) => (
<Link key={key} to={path}>
{component}
</Link>
))}
<Link to="/404">404</Link>
</div>
)
};
const routes = [
{ path: "/", component: "Home" },
{ path: "/about", component: "About" },
{ path: "/contact", component: "Contact" }
];
const createRoutes = (routes) =>
routes.map(({ component, path }) => {
const Component = components[component];
const nodeRef = React.createRef();
return (
<Route key={path} path={path} exact>
{({ match }) => {
return (
<div ref={nodeRef} className="page">
<Component />
</div>
);
}}
</Route>
);
});
const AnimatedSwitch = withRouter(({ location }) => (
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={500}
classNames="page"
unmountOnExit
>
<Switch location={location}>{createRoutes(routes)}</Switch>
</CSSTransition>
</TransitionGroup>
));
ReactDOM.render(
<React.StrictMode>
<Router>
<components.Menu links={routes} />
<AnimatedSwitch />
</Router>
</React.StrictMode>,
rootElement
);
This article explains in detail the reasoning behind it: https://latteandcode.medium.com/react-how-to-animate-transitions-between-react-router-routes-7f9cb7f5636a.
By the way withRouter is deprecated in react-router v6.
So you should implement that hook in your own.
import { useHistory } from 'react-router-dom';
export const withRouter = (Component) => {
const Wrapper = (props) => {
const history = useHistory();
return (
<Component
history={history}
{...props}
/>
);
};
return Wrapper;
};
See Deprecated issue discussion on GitHub: https://github.com/remix-run/react-router/issues/7156.
A quick fix is you can do following
<Switch>
<Route
path={"/:page(|about|contact)"}
render={() => createRoutes(routes)}
/>
<Route>
<div className="page">
<components.NotFound />
</div>
</Route>
</Switch>
Obviously not the most elegant nor scalable solution. But doable for a small react site.
Here's the forked codesandbox of working code.
https://codesandbox.io/s/react-router-switch-csstransition-catch-all-route-bug-forked-dhv39
EDIT: I was checking router.location.key first, but reaching through address bar was causing 404 page to render every page, so this is safer.
You can pass router as props to pages and conditionally render 404 page by checking router.location.pathname
export const routes = [
...all of your routes,
{
path: "*",
Component: NotFound,
},
]
{routes.map(({ path, pageTitle, Component }) => (
<Route key={path} exact path={path}>
{router => (
<CSSTransition
in={router.match !== null}
timeout={400}
classNames="page"
unmountOnExit
>
<div className="page">
<Component router={router} />
</div>
</CSSTransition>
)}
</Route>
))}
And in your 404 component, you need to check the path of the current route if it is available around all routes:
import { routes } from "../Routes";
const checkIfRouteIsValid = path => routes.findIndex(route => route.path === path);
export default function NotFound(props) {
const pathname = props.router.location.pathname;
const [show, setShow] = useState(false);
useEffect(() => {
setShow(checkIfRouteIsValid(pathname) === -1);
}, [pathname]);
return (
show && (
<div>Not found!</div>
)
);
}

How to have common components with react router?

So im trying to build a SPA (single page application) and i'm a little bit stumped as to how to include a common navbar through out the app without it being included through the whole application.
The way ive designed my code now is as follows:
ReactDOM.render(<Provider store={store}><BrowserRouter><App /></BrowserRouter></Provider>, document.getElementById('root'));
this is where i use browserouter at the root element.
and then i have in my App component:
<div>
<Header />
<Router>
<Route exact path='/' component={Post} />
{this.props.categories && this.props.categories.map((cat, i) => {
console.log(cat.path)
return <Route path={`/${cat.path}`} component={array[i]} key={i} />
})}
</Router>
</div>
can someone point out what im doing wrong and how i should do it please as i havent a clue.
in your App component use Switch instead of Router ,
You are using two routers
you can give a route for App in Browser Router
ReactDOM.render(<Provider store={store}><BrowserRouter><Route path="/" component={App} /></BrowserRouter></Provider>, document.getElementById('root'));
In React Router V4 you are encouraged to use a top-level BrowserRouter component simply to provide the history API as context. You can actually render multiple routes simultaneously at different places in your app if you like. Try using <Switch> and place your routes inside of that. This component will show the first of its children that are of type <Route> that match the current path. Also provide the exact prop to make sure that nested routes don't get displayed when a deeper nesting is available. Your final example could look something like this:
<div>
<Header />
<Switch>
{categories.map(cat => <Route {...cat} />)}
</Switch>
</div>
And inside your categories you could have the props necessary for rendering the route, like path and component. This makes your example more concise.
const Component = React.Component
const { Switch, Route, Link } = ReactRouterDOM
const Router = ReactRouterDOM.BrowserRouter
const Home = () => <h1>Home</h1>
const Post1 = () => <h1>React</h1>
const Post2 = () => <h1>Redux</h1>
const Post3 = () => <h1>Udacity</h1>
const routes = [
{
name: 'Home',
path: '/',
component: Home
},
{
name: 'React',
path: '/react',
component: Post1
},
{
name: 'Redux',
path: '/redux',
component: Post2
},
{
name: 'Udacity',
path: '/udacity',
component: Post3
}
]
const Header = () => <header>
<nav>
{routes.map(route => <Link to={route.path} key={route.path} >{route.name}</Link>)}
</nav>
</header>
class App extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<Header />
<Switch>
{routes.map(route => <Route {...route} key={route.path} exact/>)}
</Switch>
</div>
);
}
}
ReactDOM.render(
<Router><App/></Router>,
document.getElementById('container')
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-router/umd/react-router.min.js"></script>
<script src="https://unpkg.com/react-router-dom#4.2.0/umd/react-router-dom.min.js"></script>
<div id="container">
<!-- NOTE: in this embedded fiddle, React Router will not work. See https://jsfiddle.net/tay99xu0/ for working demo reference. -->
</div>

NotFound route always showing with static routes - [React Router 4]

How can I define a notFound component with React Router 4 and static routes?
I use the nextversion of react-router-redux. So, in my client:
export default function appRoutes(history) {
return (
<ConnectedRouter history={history}>
<div>
{routes.map((route, key) => (
<Route key={key} {...route} />
))}
</div>
</ConnectedRouter>
);
}
And routes is:
export const routes = [
{
path: '/',
component: App
},
{
path: '/:lang/chat',
component: Chat
},
{
path: '/:lang',
component: Listing
},
...
{
path: '*',
component: NotFound,
status: 404
},
];
With this approach, the component not found is always showing along with the matched route.
I have read that instead of wrapping the appRoutes method described above, with a div, I have to use Switch. But with this approach, the routes never match.
EDIT
I want to display, for example, App component and Listing component at the same time but the NotFound is also displaying.
So, what I'm doing wrong?
React router V4 displays all routes that match. If you would like to display just one use Switch component.
I found a solution that works for me. I have removed from static routes the App component and placed before and then I used Switch to render only one route.
export default function appRoutes(history) {
return (
<ConnectedRouter history={history}>
<div>
<Route path='/' component={App} />
<Switch>
{routes.map((route, key) => (
<Route key={key} {...route} />
))}
</Switch>
</div>
</ConnectedRouter>
);
}

React-router link doesn't work

React-router is off to a really bad start... What seems basic doesn't work. Using react-router 2.0.0 my Link component updates the URL to be /about, but my page doesn't render the About component after that...
Entry point js
var React = require('react');
var ReactDOM = require('react-dom');
var Router = require('react-router').Router;
var Route = require('react-router').Route;
var hashHistory = require('react-router').hashHistory;
var App = require('./components/App.react');
var About = require('./components/About');
ReactDOM.render(
<Router history={hashHistory} >
<Route path="/" component={App}>
<Route path="about" component={About} />
</Route>
</Router>,
document.getElementById('app')
);
App.js
'use strict';
var React = require('react');
var Link = require('react-router').Link;
var Header = require('./Header');
var UserPanel = require('./UserPanel');
var ModelPanel = require('./ModelPanel.react');
var EventPanel = require('./event/EventPanel');
var VisPanel = require('./vis/VisPanel');
var LoginForm = require('./LoginForm');
var AppStore = require('../stores/AppStore');
var AppStates = require('../constants/AppStates');
var App = React.createClass({
[... code omitted ...]
render: function() {
var viewStateUi = getViewStateUi(this.state.appState);
return (
<div>
<Header />
<Link to="/about">About</Link>
{viewStateUi}
</div>
);
}
});
For some reason, the <Link>s were not working for me with the configuration below.
// index.js
ReactDOM.render(
<Provider store={store}>
<BrowserRouter >
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
// App.js
return (
<div className="App">
<Route exact={true} path="/:lang" component={Home} />
<Route exact={true} path="/" render={() => <Redirect to={{ pathname: 'pt' }} />} />
<Route path="/:lang/play" component={Play} />} />
<Route path="/:lang/end" component={End} />
</div >
);
The Home component had the Link, but Links on the App would do the same. Every time I clicked it, it would only change the url, but the views would stay the same.
I was able to put it working when I added withRouter to the App.js
export default withRouter(connect(mapStateToProps, { f, g })(App));
I still don't understand what happened. Maybe it's related with redux or there is some detail I'm missing.
Since the 'About' route is a child of the 'App' route, you need to either add this.props.children to your App component:
var App = React.createClass({
render: function() {
var viewStateUi = getViewStateUi(this.state.appState);
return (
<div>
<Header />
<Link href="/about">About</Link>
{viewStateUi}
{this.props.children}
</div>
);
}
});
or separate your routes:
ReactDOM.render(
<Router history={hashHistory} >
<Route path="/" component={App} />
<Route path="/about" component={About} />
</Router>,
document.getElementById('app')
);
None of the solutions worked for me, including adding withRouter to my Component. I was experiencing the same issue where the browser's address bar updates the URL but the component doesn't render. During the debugging of my issue, I realize I have to present the context of my problem because it is a bit different from what the OP had.
The route I was trying to get to work was a dynamic route that takes an arbitrary parameter, e.g.
<Route path={`/hr/employees/:id`} component={EmployeePage} />
The component this route uses is "self-referential", meaning that within the component or its children components, they have a Link component that directs to /hr/employees/:id, but with a different id. So let's say if I was at /hr/employees/3 and on the page, there was a link to /hr/employees/4, e.g. <Link to='/hr/employees/4'>, I would get this problem where the component didn't re-render.
To solve this problem, I simply modified the componentDidUpdate method of my EmployeePage component:
componentDidUpdate(prevProps) {
if (this.props.match.params.id !== prevProps.match.params.id) {
// fetch data
}
}
If you're using functional components, use useEffect:
const EmployeePage = props => {
const {id} = props.match.params
useEffect(() => {
// fetch data
}, [id])
}

Resources