認証されたルートを実装しようとしていましたが、React Router 4ではこの機能が使えないことがわかりました。
<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
<Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
<Route exact path="/domains" component={DomainsIndex} />
</Route>
というエラーが出ています。
警告です。Route component>
と
を同じルートで使用してはいけません;
`は無視されます
この場合、どのような実装方法が正しいのでしょうか?
react-router` (v4) docsでは、以下のように提案されています。
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
しかし、たくさんのルートをグループ化しながらこれを実現することは可能でしょうか?
更新情報 (英語)
OK、いろいろ調べた結果、これにたどり着きました。
import React, {PropTypes} from "react"
import {Route} from "react-router-dom"
export default class AuthenticatedRoute extends React.Component {
render() {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <Route {...this.props} />
}
}
AuthenticatedRoute.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
component: PropTypes.element,
redirectToLogin: PropTypes.func.isRequired
}
アクションを render()
でディスパッチするのは正しいのでしょうか?また、componentDidMount
や他のフックを使っても正しいとは思えません。
Redirectコンポーネントを使うことになるでしょう。この問題にはいくつかの異なるアプローチがあります。私が気に入っているのは、PrivateRouteコンポーネントで、
authed`プロップを受け取り、そのプロップに基づいてレンダリングを行います。
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
これであなたのRoute
は次のようになります。
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
もし、まだよくわからないという方は、こちらの記事を参考にしてみてください - React Router v4による保護されたルートと認証
Tnx Tyler McGinnis氏によるソリューションです。 私はTyler McGinnisのアイデアから私のアイデアを作りました。
const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
return (
<Route
{...rest}
render={
decisionFunc()
? trueComponent
: falseComponent
}
/>
)
}
次のように実装できます。
<DecisionRoute path="/signin" exact={true}
trueComponent={redirectStart}
falseComponent={SignInPage}
decisionFunc={isAuth}
/>
decisionFunc は true か false を返す関数です。
const redirectStart = props => <Redirect to="/orders" />
独自のコンポーネントを作成し、それをrenderメソッドでディスパッチすることに躊躇しているようですね。そこで、<Route>
コンポーネントのrender
メソッドを使うことで、その両方を回避することができます。どうしても必要でなければ、<AuthenticatedRoute>
コンポーネントを作成する必要はありません。以下のようなシンプルなもので構いません。なお、{...routeProps}
の広がりで、<Route>
コンポーネントのプロパティを子コンポーネント(この場合は<MyComponent>
)に送り続けていることを確認してください。
<Route path='/someprivatepath' render={routeProps => {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <MyComponent {...routeProps} anotherProp={somevalue} />
} />
React Router V4 render documentation]1を参照してください。
専用のコンポーネントを作りたかったのであれば、それは正しい道のように見えます。React Router V4はpurely declarative routingなので(説明文にもそう書いてあります)、リダイレクトコードを通常のコンポーネントライフサイクルの外に置くことはできないと思います。React Router自体のコード][3]を見ると、サーバーサイドレンダリングかどうかに応じて、componentWillMount
またはcomponentDidMount
のいずれかでリダイレクトを実行しています。以下のコードは非常にシンプルなもので、リダイレクトロジックをどこに置くかを考える際の参考になるでしょう。
import React, { PropTypes } from 'react'
/**
* The public API for updating the location programatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
static defaultProps = {
push: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
}
isStatic() {
return this.context.router && this.context.router.staticContext
}
componentWillMount() {
if (this.isStatic())
this.perform()
}
componentDidMount() {
if (!this.isStatic())
this.perform()
}
perform() {
const { history } = this.context.router
const { push, to } = this.props
if (push) {
history.push(to)
} else {
history.replace(to)
}
}
render() {
return null
}
}
export default Redirect
[3]: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Redirect.js