parent routes are active when child routes are active
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from 'react-router-dom';
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route
exact
path="/tags"
render={() => {
return <Redirect to="/tags/all" />;
}}
/>
<Route path="/tags/:tagName" component={Tags} />
<Route path="/posts/:mdFile" component={Post} />
<Route path="/book" component={Book} />
<Route component={NotFound} />
</Switch>
</Router>
);
}
}
Key Notes: In component of parent route, should render {this.props.children}
.
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from 'react-router-dom';
render(
<Router>
<Route path="/" component={App}>
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo} />
</Route>
</Route>
</Router>,
document.getElementById('app')
);
App.js
: render() { return (<div>... {this.props.children}</ div>); }
.Repos.js
: render() { return (<div>... {this.props.children}</ div>); }
.const PrivateRoute = ({
component: Component,
toAuth,
...rest
}) => {
return (
<Route {...rest} render={(props) => (
auth.isAuthenticated() === true
? <Component {...props}/>
: <Redirect to={
pathname: toAuth,
state: {
from: props.location
}
}/>
)}/>
);
}
<Route path="/repos/:userName/:repoName" component={Repo} />
// In Repo.js
<div>{this.props.params.userName}</ div>
<div>{this.props.params.repoName}</ div>
<Route render={props => <Component {...props} />} />
to={
pathname: url,
state: {
...
}
}
...
const {
...
} = this.props.location.state;
replace hashHistory for browserHistory
<Redirect />
conditionally and withRouter
wrapper (this.props.history.push('nextURL')
)class Login = () => {
login = () => {
// 1. login
// 2. setState and pushHistory (500ms delay)
auth.login(() => {
const { from } = this.props.location.state || { from: { pathname: '/' }};
this.setState({ redirect: true });
this.props.history.push(from);
}, 500);
}
render() {
const { redirect } = this.state;
const { from } = this.props.location.state || { from: { pathname: '/' }};
if (redirect) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<button onClick={this.login}>Login</button>
</div>
);
}
}
// apply `history` object to props of `Login` component
export default withRouter(Login);
<ConnectedRouter history={history} basename="/react-boilerplate">
<Fragment>
<Switch>
<Route exact path={`${process.env.PUBLIC_URL}/`} component={Home} />
<Route path={`${process.env.PUBLIC_URL}/about`} component={About} />
<Route
path={`${process.env.PUBLIC_URL}/404`}
render={() => <Redirect to={`${process.env.PUBLIC_URL}/`} />}
/>
</Switch>
</Fragment>
</ConnectedRouter>;
const Header = () => (
<div>
<NavLink to={`${process.env.PUBLIC_URL}/`}>Home</NavLink>
<NavLink to={`${process.env.PUBLIC_URL}/about`}>About</NavLink>
</div>
);
publicPath: '/'
historyApiFallback: true
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './app/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js',
publicPath: '/',
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
devServer: {
historyApiFallback: true,
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html',
}),
],
};
app.use(express.static(path.resolve(__dirname, 'build')));
// always serve index.html for any request
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'build', 'index.html'));
});
# always serve index.html for any request (react-router for single page application)
root /var/www/blog/html/build;
index index.html;
server_name blog.sabertazimi.cn;
location / {
try_files $uri /index.html;
}
<Route>
instances listen to popstate
event to forceUpdate
.<Link>
/<Redirect>
, historyPush
or historyReplace
get called,
<Route>
instances re-match and re-render.const instances = [];
const register = comp => instances.push(comp);
const unregister = comp => instances.splice(instances.indexOf(comp), 1);
const historyPush = path => {
history.pushState({}, null, path);
instances.forEach(instance => instance.forceUpdate());
};
const historyReplace = path => {
history.replaceState({}, null, path);
instances.forEach(instance => instance.forceUpdate());
};
const matchPath = (pathname, options) => {
const { exact = false, path } = options;
if (!path) {
return {
path: null,
url: pathname,
isExact: true
};
}
const match = new RegExp(`^${path}`).exec(pathname);
if (!match) {
// There wasn't a match.
return null;
}
const url = match[0];
const isExact = pathname === url;
if (exact && !isExact) {
// There was a match, but it wasn't
// an exact match as specified by
// the exact prop.
return null;
}
return {
path,
url,
isExact
};
};
class Route extends Component {
static propTypes: {
path: PropTypes.string,
exact: PropTypes.bool,
component: PropTypes.func,
render: PropTypes.func
};
componentWillMount() {
addEventListener('popstate', this.handlePop);
register(this);
}
componentWillUnmount() {
removeEventListener('popstate', this.handlePop);
unregister(this);
}
handlePop = () => {
this.forceUpdate();
};
render() {
const { path, exact, component, render } = this.props;
const match = matchPath(location.pathname, { path, exact });
if (!match) {
return null;
}
if (component) {
return React.createElement(component, { match });
}
if (render) {
return render({ match });
}
return null;
}
}
Whenever a <Link>
is clicked and the location changes,
each <Route>
will be aware of that and re-match and re-render with instances
.
class Link extends Component {
static propTypes = {
to: PropTypes.string.isRequired,
replace: PropTypes.bool,
};
handleClick = event => {
const { replace, to } = this.props;
event.preventDefault();
replace ? historyReplace(to) : historyPush(to);
};
render() {
const { to, children } = this.props;
return (
<a href={to} onClick={this.handleClick}>
{children}
</a>
);
}
}
class Redirect extends Component {
static defaultProps = {
push: false,
};
static propTypes = {
to: PropTypes.string.isRequired,
push: PropTypes.bool.isRequired,
};
componentDidMount() {
const { to, push } = this.props;
push ? historyPush(to) : historyReplace(to);
}
render() {
return null;
}
}