I'm using react-transition-group to animate a router switch in React:
<CSSTransitionGroup transitionName="zoom" transitionEnterTimeout={200} transitionLeaveTimeout={200}>
<Switch key={key} location={this.props.location}>
<Route path={this.props.match.url+"/tasks/:task_id"} component={SingleTask} key={'none'} />
<Route slug={this.props.match.params.project_slug} path={this.props.match.url+"/"} render={(params) => (
<ProjectIndex {...params} slug={this.props.match.params.project_slug} />
)} key={'none'} />
</Switch>
</CSSTransitionGroup>
It was also triggering the animation whenever any sub-routes change. So, to get around that I'm getting the pathname using this.props.location.pathname, then using some really gross code to get the last segment:
pathname = pathname.split('?')[0].split('/').filter(function (i) { return i !== ""}).slice(-1)[0];
...and if it's 'tasks', 'activity' or 'notes, I'm just setting the key to 'noanimate' (i.e some generic string so the switch doesn't notice):
switch(pathname){
case 'tasks':
case 'activity':
case 'notes':
key = 'noanimate';
break;
default:
key = pathname;
break;
}
Now, the redirect from /project to /project/tasks does a double transition as it goes from 'project' to 'noanimate', and I'm not sure whether I'm meant to write some even worse string manipulation just to get either the last or second-last term, depending on whether it's 'tasks'/'activity'/'notes' or any other string.
Is there a better solution, or is that... just how we're meant to do things?
I had the same problem so I wrote my own solution: switch-css-transition-group
after install you can rewrite your code to:
import SwitchCSSTransitionGroup from 'switch-css-transition-group'
<SwitchCSSTransitionGroup location={this.props.location} transitionName="zoom" transitionEnterTimeout={200} transitionLeaveTimeout={200}>
<Route path={this.props.match.url+"/tasks/:task_id"} component={SingleTask} key={'none'} />
<Route slug={this.props.match.params.project_slug} path={this.props.match.url+"/"} render={(params) => (
<ProjectIndex {...params} slug={this.props.match.params.project_slug} />
)} key={'none'} />
</SwitchCSSTransitionGroup>
It basically go throw all routes inside this "Switch" and using matchPath function recognize if some route is properly matching.
Related
I encountered a Warning relative to the findDOMnode deprecation when trying to use react routing in combination with react-transition-group, the warning stated:
index.js:1 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node
The above warning refers to the following code:
<Route key={path} path={path} exact>
{({match})=>(
<CSSTransition
in={match!=null}
timeout={300}
classNames="page"
unmountOnExit
mountOnEnter
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>)
My first attempt of getting rid of that warning was to make use of useRef as suggested:
const nodeRef = useRef(null);
passing nodeRef as ref prop of the CSStransation element but the warning was still showing up.
For some reason I could only get rid of the warning by passing the triggering event that I was also using in the 'in' prop of the CSStransition element, like showed here below:
<Route key={path} path={path} exact>
{({match})=>(
<CSSTransition
ref={nodeRef}
in={match!=null}
timeout={300}
classNames="page"
unmountOnExit
mountOnEnter
key={match!=null} <------------ Adding this removed the warning
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>)
Everything work smoothly now but I cant really undestand why, and even if I remove the the ref from the CSStransition element I dont get any warning anymore.
Does anybody undestand why this is actually happening?
I spent a while trying to figure this out as well, and I finally got it! You need to use the nodeRef prop in the CSSTransition for each route individually. Each route gets its own ref, and that ref needs to be assigned to the nodeRef accordingly. I was able to get this working by using an array of refs, mapping each route and assigning the refs to the current index.
Take a look at this working example I made:
https://codesandbox.io/s/react-transition-routes-with-noderef-k9q47
And here's the block of code that is going to be the most helpful:
////// CONTENT TRANSITION ROUTER
const PageContent = withRouter(({ location }) => {
let routeRefs: any[] = [];
const isMatch = useCallback(
(path: string): boolean => {
return location.pathname === path ? true : false;
},
[location]
);
return (
<>
{appRoutes.map(({ path, Component }, index) => {
routeRefs[index] = React.useRef(null);
return (
<Route key={index} exact path={path}>
{() => {
// Route callback ensures the transitions are loaded correctly
return (
<CSSTransition
nodeRef={routeRefs[index]}
in={isMatch(path)}
timeout={300}
classNames="fade"
unmountOnExit
appear
>
<div ref={routeRefs[index]} className="fade">
<Component />
</div>
</CSSTransition>
);
}}
</Route>
);
})}
</>
);
});
I have the goal of generating stateful information as if I had navigated to a path manually, when accessing that the path directly from outside of the "application".
For example:
The base component Index uses the state to determine which set of routes to consider:
render() {
const $ = this;
return (
<SiteConfig.Provider value={$.store}>
<BrowserRouter>
<React.StrictMode>
<Route path="/:root" component={PathFinder}/>
{(() => {
if ($.state.mode === 'web') {
return (
<>
<Header/>
<Banner/>
<Switch>
<Route path="/" exact component={Web}/>
.
.
.
</Switch>
<Footer/>
</>
);
} else if ($.state.mode === 'db'){
return (
<>
<Header/>
<Switch>
<Route path="/db" exact component={DB}/>
.
.
.
</Switch>
</>
);
} else {
return (
<>
<ErrorPage/>
</>
);
}
})()}
</React.StrictMode>
</BrowserRouter>
</SiteConfig.Provider>
);
}
However this does not work as the rendering of of the Route component which instantiates a PathFinder component to determine the page's path - is done after the default state is set for the rendering of the Index component.
The Header component in turn generates its links based off of a contextual store type data structure:
getNavLinks() {
const $ = this;
switch ($.context.get('mode')) {
case 'database':
return navLinksDB;
default:
case 'web':
return navLinksWeb;
}
}
But the Header is also responsible for the state's mode:
<Button variant={'outline-success'} as={NavLink} className="mr-2" activeClassName="active" to={'/db'} onClick={$.toggleSiteMode.bind($)}>Change View</Button>
and it calls this
toggleSiteMode() {
const $ = this;
if ($.context.get('mode') === 'web')
$.context.set('mode', 'database');
else
$.context.set('mode', 'web');
}
This store has an event emitter which updates the state on the Index and so - this is done too late and is missed at the loading of the page.
How can I make an access to '~/db' and regenerate this stateful information?
I don't want to use more and more packages, I'm already using react-router-domand the site size is absurd at something in the vicinity of 400KiB compiled. I also want to avoid using nested routes as all that serves to do is hide away from this issue of generating stateful information based on the current path.
I'm very much aware of the react-router-dom I want to do a conditional rendering of the component. If use is not logged in redirect him to some third party URL
for example, the below code looks neat and works fine
<Route exact path="/home" render={() => (
isLoggedIn() ? (
<Redirect to="/front"/>
) : (
<Home />
)
)}/>
Let's say in the above example if I want to redirect to https://www.google.com how can I do it?
if I write
<Redirect to="https://www.google.com"> it gives me error.
How can I redirect to a third party website?
You can use a tag for external urls,
<a href='https://domain.extension/external-without-params'>external</a>
but also you can provide component like this:
<Route path='/external' component={() => { window.location = 'https://domain.extension/external-without-params'; return null;} }/>
You can use window.open() to redirect to any website.
For example:
window.open('https://www.google.com');
implement redirection in your code:
render () {
if(this.isLogin) {
return(/* your components*/);
} else {
window.open('https://www.google.com');
return (<div></div>); //render function should return something
}
}
You can also specifies the target attribute or the name of the window, for more details, see w3school tutorial about this function.
In case you're using Typescript you probably will get the following error with the Accepted Answer:
Type 'string' is not assignable to type 'Location'
To fix that you just need to use window.location.href
<Route path="/external" component={() => {window.location.href = config.UPGRADE_URL return null }} />
Have on mind that you cannot perform that with <Link /> or <NavLink /> since they're mainly for routing throughout Single Page Application.
You should use anchor tag instead.
Example: Google.
Can easily use a button just do this:
<button onClick={(e) => (window.location = 'https://www.google.com')}>Click me</button>
I have a component that i need to pass props into while being rendered using the Route component. The problem is that typescript disallows lambda functions in JSX and i just can't figure out how to rewrite this into an allowed function:
<Route
key={index}
exact={true}
path={item.urlAddress}
render={() => <DocumentView data={data} />}
/>
I tried this, but it is still being evaluated as a lambda, don't know why:
render={function() { <DocumentView /> }}
Anyone knows how to rewrite it?
Edit: I know this is a linter issure, and i'm looking for the proper way to write this according to ts-lint standards. I know i can add a rule exception, but i want to know how to do this the "proper" way, not adding exceptions every time i encounter a linting problem
You can make a change in the tslint rules to allow for arrow functions in render. However, since you asked an alternative way to write the above. It would be written like
renderDocument = (props) => {
return <DocumentView data={data} {...props}/>
}
render() {
return (
<Route
key={index}
exact={true}
path={item.urlAddress}
render={this.renderDocument}
/>
)
}
Can I use the same path with different component?
For example,
<Route exact path="/add/:addId" component={AddIdAType} />
<Route exact path="/add/:addId" component={AddIdBType} />
and my code is about this
goPage(type){
switch(type){
case 'a' :
return <AddIdAType />
default:
return <AddIdBType />
}
}
render(){
let go= this.goPage(this.props.type);
return (
<Link to={`/add/${add.id}/`} component={go}>
........
</Link>
This way is not correct, so what should I can do to go to different component but same path (component={go} is ERROR)
If someone know about it, please help me to fix it out
Thank you
You can do it in a single Route using the render method of Route.
<Route exact
path="/add/:addId"
render={(props)=>{
let id = props.match.params['addId']; //i guess you would use this field in the child components
switch(this.props.type){
case 'a' :
return <AddIdAType addId={id}/>
default:
return <AddIdBType addId={id} />
}
}} />