Going to another route must unmount current route component - react-router

Here is my use case, I made a simple example in order to ask this question, let's say I have two routes, /view1 and /view2, each mounting a component, View1 and View2, I need View1 to unmount when I go to /view2.
Here is the code if you want to try:
import React from 'react';
import ReactDOM from 'react-dom';
import {Router, Route, Link, browserHistory} from 'react-router';
import Portal from 'react-portal';
class View1 extends React.Component {
constructor(context, props) {
super(context, props)
}
componentDidMount() {
console.log('view 1 mounted');
}
componentWillUnmout() {
console.log('view 1 unmount');
}
render() {
return (
<h2>hanta</h2>
);
}
}
class View2 extends React.Component {
constructor(context, props) {
super(context, props)
}
componentDidMount() {
console.log('view 2 mounted');
}
componentWillUnmout() {
console.log('view 2 unmount');
}
render() {
return (
<h2>View 2</h2>
);
}
}
class View extends React.Component {
render() {
return (
<div>
<h1>View</h1>
<Link to="/view1">view 1</Link>
<Link to="/view2">view 2</Link>
{this.props.children}
</div>
);
}
}
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={View}>
<Route path="/view1" component={View1} />
<Route path="/view2" component={View2} />
</Route>
</Router>
, document.getElementById("container"))
ComponentWillUnmout doesn't get called in any case, I'm using:
├── react#0.14.6
├── react-dom#0.14.6
├── react-router#2.0.0-rc5

You spelled it wrong. It should be componentWillUnmount, not componentWillUnmout.

Related

exitBeforeEnter is not working as intended - Trouble w/ Page Transitions

I'm trying to create a smooth page transition using a combination of react-router-dom and framer-motion, and I'm trying to have my pages fade out on exit and fade in on enter. But exitBeforeEnter is not working how it's supposed to. The page will not fade out on exit but the next page will fade in every time. Below is my code, and I'll attach one of the page files (all of the pages have pretty much the same code).
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));
App.js
import React from 'react';
import About from "./pages/About.js"
import Home from "./pages/Home.js"
import Projects from "./pages/Projects.js"
import { AnimatePresence } from 'framer-motion'
import { BrowserRouter as Switch, Route, useLocation } from "react-router-dom";
function App() {
const location = useLocation();
return (
<div className="App">
<AnimatePresence exitBeforeEnter>
<Switch location={location} key={location.pathname}>
<Route path="/about" component={About} />
<Route path="/projects" component={Projects} />
<Route path="/" exact component={Home} />
</Switch>
</AnimatePresence>
</div>
);
}
export default App;
Home.js (Page File)
import React from 'react';
import '../css/main.css';
import '../css/index.css';
import particleText from '../components/ParticleText.js'
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion'
const pageVariants = {
in: {
opacity: 1,
transition: {
duration: 1
}
},
out: {
opacity: 0,
transition: {
duration: 1
}
}
}
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
try { particleText(true) } catch { /* Error */ }
return (
<motion.div className="main" initial="out" animate="in" exit="out" variants={pageVariants}>
<div className="introOverlay"></div>
<Link to="/about" className="homeButtonContainer">
<p className="homeButtonText">About Me</p>
</Link>
<Link to="/projects" className="homeButtonContainer">
<p className="homeButtonText">My Projects</p>
</Link>
</motion.div>
);
}
}
export default Home;
The property exitBeforeEnter is deprecated now, it must be replaced with mode='wait':
<AnimatePresence mode='wait'>
...
<AnimatePresence>
That way, the change between components will be seamless since the first component will unmount before loading the next one with its respective animations.
Its a bit late but in case you still looking...
The exit animation will NOT take place if AnimatePrecence it self is unmounting from the react tree.
Try this in your App.js :
return (
<Router>
<Route
render={ ({location}) => (
<AnimatePresence initial={ fase } exitBeforeEnter>
<Switch location={ location } key={ location.pathname }>
<Route
exact
path='/'
render={ () => <Home /> }
/>
<Route
exact
path='/projects'
render={ () => <Projects /> }
/>
<Route
exact
path='/about'
render={ () => <About/> }
/>
</Switch>
</AnimatePresence>
) }
/>
</Router>
);
You can leave the callback, render for eg, I just include it for clarity.
The initial={false} is just for disabling the initial animation for subsequent page reload;
If this doesn't work, make sure to keep the same variant structure the same in all your other components.

React Router is showing only one of the Switch components and does nothing when others are called

My routing component looks like this:
import { Link, Route, Switch } from 'react-router-dom';
import Nav from 'react-bootstrap/Nav'
import AllWorkouts from './Workout/AllWorkouts/AllWorkouts';
import WorkoutCreate from './Workout/WorkoutCreate/WorkoutCreate';
import WorkoutDetails from './Workout/WorkoutDetails/WorkoutDetails';
import WorkoutEdit from './Workout/WorkoutEdit/WorkoutEdit';
const Main = () => {
return (
<Switch>
<Route path="/workout/:id/edit" componnet={WorkoutEdit} />
<Route path="/workout/:id/details" componnet={WorkoutDetails} />
<Route path="/workout/create" componnet={WorkoutCreate} />
<Route path="/workout/all" component={AllWorkouts} />
</Switch>
);
}
export default Main;
I have included BrowserRouter in index.js and the only route that is matching is path="/workout/all". I cannot call any of the other routs with Link or directly in the URL.
When I call /workout/all I can see the component with all the other routes nothing happens.
Thank you!
Below is the other component and I do not see any misspelling error:
import { Link, Route, Switch } from 'react-router-dom';
import Button from 'react-bootstrap/Button'
const AllWorkouts = () => {
return (
<div>
<Link to="/workout/create">
<Button variant="outline-primary">Create new workout</Button>{' '}
</Link>
<h1>Hello from AllWorkouts</h1>
</div>
);
}
export default AllWorkouts;
You have misspelled component in the other routes
mport { Link, Route, Switch } from 'react-router-dom';
import Nav from 'react-bootstrap/Nav'
import AllWorkouts from './Workout/AllWorkouts/AllWorkouts';
import WorkoutCreate from './Workout/WorkoutCreate/WorkoutCreate';
import WorkoutDetails from './Workout/WorkoutDetails/WorkoutDetails';
import WorkoutEdit from './Workout/WorkoutEdit/WorkoutEdit';
const Main = () => {
return (
<Switch>
<Route path="/workout/:id/edit" component={WorkoutEdit} />
<Route path="/workout/:id/details" component={WorkoutDetails} />
<Route path="/workout/create" component={WorkoutCreate} />
<Route path="/workout/all" component={AllWorkouts} />
</Switch>
);
}
export default Main;

Unexpected re-rendering of components

I'm trying to navigate back to a Menu component, but when I trigger a route to do so, the previous rendered Practice component is rendered in the root domain level, which should just be for the Menu component.
The solutions for similar questions on StackOverflow say to use exact in the route, but as you can see I have this in place.
How do I make this work as expected? Thanks.
Here's what I have...
App.js
import React from 'react';
import './App.css';
import PracticeContextProvider from './contexts/PracticeContext';
import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
import Menu from './pages/Menu';
function App() {
return (
<div className="app">
<PracticeContextProvider>
<Menu />
</PracticeContextProvider>
</div>
);
}
export default App;
Menu.js
import React, { useContext } from 'react';
import {BrowserRouter as Router, Route, Switch, Link, useParams, useLocation, useRouteMatch} from 'react-router-dom';
import { PracticeContext } from '../contexts/PracticeContext';
import Practice from './Practice';
import ModuleLanguageSelector from '../components/ModuleLanguageSelector';
import ModuleListMenuItem from '../components/ModuleListMenuItem';
const Menu = () => {
const { modulesJson } = useContext(PracticeContext);
return (
<div>
<h1>Menu</h1>
<Router>
<Switch>
<Route exact path="/">
<ModuleLanguageSelector />
{ modulesJson && (
modulesJson.map(module => {
return (
<Link to={'/Practice/' + module._id} key={ module._id }>
<ModuleListMenuItem module={ module }></ModuleListMenuItem>
</Link>
)
})
)}
</Route>
<Route exact path="/Practice/:moduleId" component={Practice} />
</Switch>
</Router>
</div>
);
}
export default Menu;
Practice.js
import React, { useContext } from 'react';
import {BrowserRouter as Router, Route, Switch, Link, useParams, useLocation, useRouteMatch} from 'react-router-dom';
import { PracticeContext } from '../contexts/PracticeContext';
import Menu from './Menu';
import ModulePracticeAnswerArea from '../components/ModulePracticeAnswerArea';
import ModulePracticeQuestion from '../components/ModulePracticeQuestion';
import ModulePracticeProgress from '../components/ModulePracticeProgress';
import ModulePracticeTutorial from '../components/ModulePracticeTutorial';
const Practice = () => {
const { moduleId } = useParams();
const { questionIndex } = useContext(PracticeContext);
return (
<Router>
<div className="module-practice-screen">
<h1>Practice</h1>
<div className="module-practice-container">
<Link to={'/'}>
<button className="quit-practice">X</button>
</Link>
{
moduleId ?
<React.Fragment>
{/*<ModulePracticeTutorial moduleId={ moduleId } />*/}
<ModulePracticeProgress questionNumber={ questionIndex } />
<ModulePracticeQuestion moduleId={ moduleId } questionNumber={ questionIndex } />
<ModulePracticeAnswerArea moduleId={ moduleId } questionNumber={ questionIndex } />
</React.Fragment>
:
<h3>The menu should appear here!</h3>
}
</div>
</div>
<Switch>
<Route exact path="/" component={Menu} />
</Switch>
</Router>
);
};
export default Practice;
You don't need <Router> and <Switch> in Practice
const Practice = props => {
const { moduleId } = useParams();
const { questionIndex } = useContext(PracticeContext);
return (
<div>
<div className="module-practice-screen">
<h1>Practice</h1>
<div className="module-practice-container">
<Link to="/">
<button>X</button>
</Link>
{moduleId ? (
<React.Fragment>
{/*<ModulePracticeTutorial moduleId={ moduleId } />*/}
<ModulePracticeProgress questionNumber={questionIndex} />
<ModulePracticeQuestion
moduleId={moduleId}
questionNumber={questionIndex}
/>
<ModulePracticeAnswerArea
moduleId={moduleId}
questionNumber={questionIndex}
/>
</React.Fragment>
) : (
<h3>The menu should appear here!</h3>
)}
</div>
</div>
</div>
);
};
codesandbox

Testing react-router (with Enzyme) with initialEntries

I'm having an issue testing react-router starting with an initialEntries value - the following test fails, and I'm not sure why or what I'm doing wrong.
import React, { Component } from 'react';
import { assert } from 'chai';
import { MemoryRouter, BrowserRouter as Router, Route } from 'react-router-dom';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
/* eslint react/prefer-stateless-function: "off" */
class TestApp extends Component {
render() {
return (
<Router>
<div>
<Route path="/nothome" render={() => 'AWAY'} />
<Route path="/" exact render={() => 'HOME'} />
</div>
</Router>
);
}
}
describe('react-router works with enzyme', () => {
it('works with enzyme with starting location', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/nothome']} initialIndex={0}><TestApp /></MemoryRouter>);
assert.isTrue(wrapper.html().includes('AWAY'), wrapper.html());
});
});
The test fails with the following:
● react-router works with enzyme › works with enzyme with starting location
AssertionError: <div>HOME</div>: expected false to be true
I think I understand now... wrapping the component in <MemoryRouter> doesn't override the existing <Router> component. The following test passes:
import React, { Component } from 'react';
import { assert } from 'chai';
import { MemoryRouter, Route } from 'react-router-dom';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
/* eslint react/prefer-stateless-function: "off" */
class TestApp extends Component {
render() {
return (
<div>
<Route path="/nothome" render={() => 'AWAY'} />
<Route path="/" exact render={() => 'HOME'} />
</div>
);
}
}
describe('react-router works with enzyme', () => {
it('works with enzyme with starting location', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/nothome']} initialIndex={0}><TestApp /></MemoryRouter>);
assert.isTrue(wrapper.html().includes('AWAY'), wrapper.html());
});
});

Simple React router not working

Can't for the life of me get a simple react-router to work! It displays the first page, but when I click the link it doesn't do anything but change the url to /home. It keeps showing "app". Why is the home component not loading???
Simple code:
import React from 'react';
import { Link } from 'react-router'
import { render } from 'react-dom'
import { Router, Route } from 'react-router'
class App extends React.Component {
render() {
return (
<div><Link to="/home">app</Link></div>
);
}
}
class Home extends React.Component {
render() {
return (
<div>Honey, I'm home!!!</div>
);
}
}
render(
<Router>
<Route path="/" component={App}>
<Route path="home" component={Home}/>
</Route>
</Router>,
document.getElementById('tempoot')
)
Because you haven't told it to render Home anywhere. You need to do something like (core thing you are missing is {this.props.children}):
import React from 'react';
import { Link } from 'react-router'
import { render } from 'react-dom'
import { Router, Route } from 'react-router'
class App extends React.Component {
render() {
return (
<div>
<div className="nav">
<Link to="/home">app</Link>
</div>
<div className="content">
// THIS IS THE CORE LINE YOU ARE MISSING
{this.props.children || "No one is home :("}
</div>
</div>
);
}
}
class Home extends React.Component {
render() {
return (
<div>Honey, I'm home!!!</div>
);
}
}
render(
<Router>
<Route path="/" component={App}>
<Route path="home" component={Home}/>
</Route>
</Router>,
document.getElementById('tempoot')
)