React router 4 beta 2 with ReactCSSTransitionGroup - react-router

I prefer ReactCSSTransitionGroup for react-motion. The below code causes the component to fade in (appear) on route change, BUT the issue is - the leaving component does not fade out, it leaves instantly.
<Switch>
<FadeRoute exact path="/" component={PageLanding}/>
<FadeRoute path="/login" component={PageLogin}/>
<FadeRoute path="/signup" component={PageSignup}/>
<FadeRoute component={Page404}/>
</Switch>
function FadeRoute({component:Component, ...rest}) {
return (
<Route {...rest} children={({location,match}) => (
<ReactCSSTransitionGroup {...{key:Date.now(), transitionName:'fade', transitionAppear:true, transitionEnter:true, transitionLeave:true, transitionAppearTimeout:300, transitionEnterTimeout:300, transitionLeaveTimeout:300})}>
<Component/>
</ReactCSSTransitionGroup>
)} />
);
}
<style>
.fade-enter, .fade-appear { opacity:0; }
.fade-enter.fade-enter-active,
.fade-appear.fade-appear-active { opacity:1; transition: opacity 300ms; }
.fade-leave { opacity:1; }
.fade-leave.fade-leave-active { opacity:0; transition: opacity 300ms; }
</style>

The way that a <Switch> works is that it only renders the first <Route> whose path is matched by the current location. This means that when you navigate, the existing <FadeRoute> is immediately unmounted when you navigate. Its child <CSSTransitionGroup> is also unmounted, so it has no opportunity to run the leave transition for its children.
What you want to do is to wrap your <Switch> in a <CSSTransitionGroup>. The <Switch> is the component that will be transitioned, so it should have a key. You should also pass it the location object so that when a <Switch> is leaving, it animates using the old location instead of the current one.
<CSSTransitionGroup
transitionName='fade'
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
<Switch key={location.pathname} location={location}>
<Route path="/red" render={Red} />
<Route path="/green" render={Green} />
<Route path="/blue" render={Blue} />
</Switch>
</CSSTransitionGroup>

Related

Can we put html inside Route element?

<Router>
<Routes>
<Route path='/' element={<Navbar />} />
<Route path='/' element={<div className='recipes'>
{query ? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>
)) : "Loading"}
</div>}/>
<Route path='/' element={<Details />} />
</Routes>
</Router>
For example in the above code, I want to render the HTML along with the Route element.
I am not getting the 2nd and 3rd Route tags displayed on my localhost. Where is my mistake?
What is the correct way to do this?
The only requirement is that the element prop takes a React.ReactNode. In other words, it takes any valid JSX.
The issue though is that there can only be one route per path. Your code is trying to render 3 routes on the same "/" path. Just unconditionally render the Navbar and Details components not on a route.
Example:
<Router>
<Navbar />
<Routes>
<Route
path='/'
element={(
<div className='recipes'>
{query
? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>))
: "Loading"
}
</div>
)}
/>
</Routes>
<Details />
</Router>
If you are wanting to conditionally render Navbar and Details on only certain routes then create a layout route component.
Layout Route
Outlet
Example:
import { Outlet } from 'react-router-dom';
const Layout = () => (
<>
<Navbar />
<Outlet /> // <-- nested routes render element content here
<Details />
</>
);
Render the routes you want to have the navbar and details as nested routes, and for the routes you don't want them render these as sibling routes.
<Router>
<Routes>
<Route element={<Layout />}>
<Route
path='/'
element={(
<div className='recipes'>
{query
? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>))
: "Loading"
}
</div>
}
/>
...other routes with Navbar and Details...
</Route>
...other routes w/o Navbar and Details...
</Routes>
</Router>

How can I put a class as a element parameter in react router?

function App() {
return (
<Router>
<Routes>
<Route path="/nav" element={Nav()}/>
<Route path="/page1" element={Page1()}/>
<Route path="/page2" element={Page2()}/>
<Route path="/map" element={Mapp()}/>
</Routes>
</Router>
);
}
The above is my function, which I render. nav, page1, and page2 all work fine since I wrote them as functions. However, the "Mapp" is a class. How can I make this work?
In react-router-dom v6 the Route component's element prop takes a ReactElement, i.e JSX. I suspect the Nav, Page, and Page2 function components work because they take no props and return JSX. If these are actually React components they should be passed as JSX. Same goes for class-based components, pass them as JSX.
function App() {
return (
<Router>
<Routes>
<Route path="/nav" element={<Nav />}/>
<Route path="/page1" element={<Page1 />}/>
<Route path="/page2" element={<Page2 />}/>
<Route path="/map" element={<Mapp />}/>
</Routes>
</Router>
);
}

What's the best way to change a global component based on the current route?

I'm building a website with React and I would like my navbar to have a different background color depending on the current route. My App.js looks like this:
class App extends React.Component {
render() {
return (
<Router>
<div className="App">
<div className="Navbar" >
<Navbar />
</div>
<div className="Content">
<Route path="/" exact render={() => <LandingPage />} />
<Route path="/about" exact render={() => <AboutPage />} />
<Route path="/contact" exact render={() => <ContactPage />} />
</div>
</div>
</Router>
);
}
}
Suppose I want a transparent background on the Home ("/") page, but a solid color everywhere else, what is the best way to go about changing the background property in <Navbar />'s CSS to achieve what I want?
Suppose I stored the CSS properties I'd like to change in this.state, and then call a function to change these whenever there is a route change?
You can use context api for changing the background color of any of your components. So, you just need to define a context value based on the different urls.

React Router - are nested <Switch> components an anti-pattern?

From React Router's docs:
All children of a <Switch> should be <Route> or <Redirect> elements. Only the first child to match the current location will be rendered.
Nonetheless, nested <Switch> statements are allowed. I use the pattern to break up large numbers of <Routes>:
<Switch>
<Route path="/foo" component={FooRouter} />
<Route path="/bar" component={BarRouter} />
<Route path="/baz" component={BazRouter} />
</Switch>
...
const FooRouter = () => (
<Switch>
<Route exact path="/foo/:id" component={ViewFoo} />
<Route exact path="/foo/new" component={NewFoo} />
</Switch>
)
const BarRouter = () => (
<Switch>
<Route exact path="/bar/new" component={NewBar} />
</Switch>
)
....
Curious if there is a better method for breaking up large numbers of routes and if nested <Switch> statements should be avoided?
as you solve it just fine when you have a lot of nested route yo can speared them across the app and make a dynamic routes
but soon react-router-dom v6 will be release with a huge upgrade one of them is useRoutes
that let you configure your routes like this:
let element = useRoutes([
// A route object has the same properties as a <Route>
// element. The `children` is just an array of child routes.
{ path: '/', element: <Home /> },
{
path: 'users',
element: <Users />,
children: [
{ path: '/', element: <UsersIndex /> },
{ path: ':id', element: <UserProfile /> },
{ path: 'me', element: <OwnUserProfile /> },
]
}
]);
introduction to react-router-dom v6 they have some cool new feature that worth to watch for
one of them is the replace of with witch help you a lot with nested routes and fun thing you don't gonna need to use the exact anymore
<Routes>
<Route path="/" element={<UsersIndex />} />
<Route path=":id" element={<UserProfile />} />
<Route path="me" element={<OwnUserProfile />} />
</Routes>
this is how it gonna look with the new feature
A note on nested conditional routes: Switch must only have Route children. If you declare Switch inside Switch, every route after Switch won't be used, i.e.
<Switch>
<Route path="/1" />
<Switch> ... </Switch>
<Route path="/2" /> // this one won't work!
</Switch>
So don't do this, stick to declaring one route per condition or render routes as an array under common condition:
<Switch>
{condition && <Route path="/1" >}
{condition && <Route path="/2">}
{/* or */}
{anotherCondition && [
// notice `key`. React will warn you about rendering a list without key prop
<Route key="3" path="/3">,
<Route key="4" path="/4">,
]}
</Switch>
This is true for react-router-dom v5, not sure about 6.

Trouble migrating to react-router v4

I am having trouble migrating to react router 4 with nested routes. Here was some snippets from my previous code. was my layout container and everything was rendered within that if logged in (otherwise redirect to login)
ReactDOM.render(
<Provider store={ store }>
<div>
<Router history={ history }>
<Route path='/login' component={ Login } />
<Route path="/password/reset" component={PasswordReset} />
<Route path='/register' component={ Register } title={ 'Register' } />
<Route path='/password/change/:token' component={ ChangePassword } title={ 'Register' } />
<Route component={ EnsureLoggedInContainer }>
<Redirect from='/' to='/dashboard' />
<Route path='/' component={ App }>
<Route path='/logout' component={ Logout } />
....
</Router>
</div>
within to render the children components:
class ContentLayout extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let children = null;
if (this.props.children) {
children = React.cloneElement(this.props.children, {
updateHeader: this.props.updateHeader,
});
}
return (
<div className={ this.props.cls }>
<MainHeader
updateHeader={ this.props.updateHeader }
header={ this.props.header }
/>
{children}
</div>
);
}
}
With v4 I've been trying to figure out the proper way to render as the layout and any child components within. So far I've been trying to get it working but feel I am on wrong path. (Currently props.match always points to '/')
ReactDOM.render(
<Provider store={ store }>
<ConnectedRouter history={history}>
<div>
<Switch>
<Route exact path='/login' component={ Login } />
<Route exact path="/password/reset" component={PasswordReset} />
<Route exact path='/register' component={ Register } />
<Route exact path='/password/change/:token' component={ ChangePassword } />
<Route path='/' component={ App } />
</Switch>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('app')
);
Within App
const RouteWithProps = ({ component: Component, props, ...extraProps})=> {
return (<Route
{...extraProps}
render={() => <Component {...props} {...extraProps} />}
/>
);
}
and with the component rendering
{securedRoutes.map((route, i) => (
<RouteWithProps key={i} {...route} updateHeader={this.props.updateHeader} location={this.props.location} match={this.props.match} />
))}
What is the proper way or a good example of how to structure the app so for all logged in routes the layout is
<App>
<ContentLayout>
<Child>
with App passing props like updateHeader and anything else to all children.
I got it working by removing passing location and match to RouteWithProps.
I had an issue with the RouteWithSubRoutes example in dealing with nested routes for things like /.../:id and /.../:submit ended up doing this to make work so I can continue working. Do not think this is ideal but will work till another answer on best practices.
const RouteWithProps = ({ component: Component, ...extraProps }) => {
return (<Route exact path={extraProps.path}
{...extraProps}
render={matchProps => <Component {...matchProps} {...extraProps}
/>}
/>
);
}
Also removed passing this.props.location and match to this component.