やろうやろうと思いつつ中々実現できなかったヤツ
やっと大体できたので投稿
この構造がベストかと言えばそうでもないような気がするけど
やってみるといいReduxの練習になったと思う
Email Verificationと多言語はまだ未対応
追記:Email Verificationについては以下を参考にした
Laravel:Email Verification(メールアドレスの確認)を使う
無駄にClass Componentになってるものはおいおい修正していきます
準備
プロジェクトの準備とか
DBの準備とかメールサーバーとかの.envの設定は省略
React関連のパッケージのインストールとデフォルトが古いのでアップデートしておく
$ composer create-project --prefer-dist laravel/laravel LaravelReact
$ cd LaravelReact
$ php artisan preset react
$ npm install --save-dev react react-dom connected-react-router history prop-types
$ npm install --save-dev react-bootstrap@next react-redux react-router react-router-dom
$ npm install --save-dev redux redux-persist @babel/preset-react axios bootstrap laravel-mix
PHP側の追加
ルートの追加
Auth::routes()で認証関連の処理が登録されるので
後から画面系のルートを名前付きで上書き
ワイルドカード受付は一番最後に書く
-use Illuminate\Contracts\Auth\MustVerifyEmail;
+use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract;
+use Illuminate\Auth\MustVerifyEmail;
-class User extends Authenticatable
+class User extends Authenticatable implements MustVerifyEmailContract
{
- use Notifiable;
+ use MustVerifyEmail, Notifiable;
Route::get('/', function(){
return view('welcome');
});
+Auth::routes(['verify' => true]);
+Route::get('/login', 'ReactController@index')->name('login');
+Route::get('/register', 'ReactController@index')->name('register');
+Route::get('password/reset', 'ReactController@index')->name('password.request');
+Route::get('password/reset/{token}', 'ReactController@index')->name('password.reset');
+Route::get('email/verify', 'ReactController@index')->name('verification.notice');
+Route::post('/session', 'ReactController@session')->name('session');
+Route::get('/{router}', 'ReactController@index')->name('home');
コントローラーの追加
画面表示用のindexとログインセッション判定用のsessionを定義
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ReactController extends Controller
{
public function index(Request $request)
{
return view('react');
}
public function session(Request $request)
{
return $request->user();
}
}
テンプレートの追加
Laravelのステージ情報をlaravelStatus、エラー情報をlaravelErrorsとしてグローバル定義
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="react-root"></div>
<script>
var laravelSession = {};
laravelSession['status']=@if(session('status'))'{{session('status')}}'@else''@endif;
laravelSession['resent']=@if(session('resent'))'{{session('resent')}}'@else''@endif;
var laravelErrors=@php print(htmlspecialchars_decode($errors))@endphp;
</script>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
</body>
</html>
JS側の追加
構成概要
app.js
bootstrap.js
ReactRoot.js
stores
┗
antions
┗
reducers
┗
routes
┗
components
┗Menus
┗Forms
┗Elements
外枠の定義
以前の記事ReactのClass ComponentでReact-Reduxに繋ぐにて作った構成を元に配備
一旦ログインセッションを問い合わせてからその結果を持ってからレンダリング処理へ
bootstrap.jsは既存のままで利用
require('./bootstrap');
// ↓以下を追記
import { Provider } from 'react-redux'
import React from 'react'
import ReactDOM from 'react-dom'
import ReactRoot from './ReactRoot'
import configureStore, { history } from './stores/configureStore'
import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'
const store = configureStore()
const pstore = persistStore(store)
const render = (props) => {
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={<p>loading...</p>} persistor={pstore}>
<ReactRoot history={history} responseSession={props} />
</PersistGate>
</Provider>,
document.getElementById('react-root')
)
}
function authSession()
{
let params = new URLSearchParams();
let url = '/session';
window.axios.post(url,params)
.then((response)=>{
render(response.data)
})
}
authSession()
フォーム要素に対しエラーがあれば再描画エラーがなければリロードとしてフォーム要素をクリアするためReduxアクションを呼び出し
適用遅延?があるようなのでコンストラクタで設定したredux状態ではなく上のコンポーネントから受け取ったログイン状態を
ルート振り分けに渡す
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import {
setSession,
setName,
setEmail,
setRemember,
setCSRF
} from './actions/authentications'
import routes from './routes'
class ReactRoot extends React.Component {
constructor(props){
super(props)
props.setSession(props.responseSession)
if(laravelErrors['name']===undefined
&&laravelErrors['email']===undefined
&&laravelErrors['password']===undefined)
{
this.props.setName('')
this.props.setEmail('')
this.props.setRemember(false)
}
let token = document.head.querySelector('meta[name="csrf-token"]')
this.props.setCSRF(token.content)
}
render(){
return (
<ConnectedRouter history={this.props.history}>
{ routes(this.props.responseSession) }
</ConnectedRouter>
)
}
}
ReactRoot.propTypes = {
history: PropTypes.object,
csrf: PropTypes.string,
setSession: PropTypes.func.isRequired,
setName: PropTypes.func.isRequired,
setEmail: PropTypes.func.isRequired,
setRemember: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
csrf: state.csrf,
})
const mapDispatchToProps = dispatch => ({
setCSRF: (csrf) => dispatch(setCSRF(csrf)),
setSession: (session) => dispatch(setSession(session)),
setName: (name) => dispatch(setName(name)),
setEmail: (email) => dispatch(setEmail(email)),
setRemember: (remember) => dispatch(setRemember(remember)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ReactRoot)
Store
ここも前回の記事参照
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from '../reducers'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const persistConfig = {
key: 'root',
storage,
blacklist: ['router'],
}
export const history = createBrowserHistory()
const persistedReducer = persistReducer(persistConfig, createRootReducer(history))
export default function configureStore(preloadedState) {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
persistedReducer,
preloadedState,
composeEnhancer(
applyMiddleware(
routerMiddleware(history),
),
),
)
return store
}
Action
ログインセッション、フォーム利用要素(名前、メール、記憶チェック)、CSRFトークン、パスワードリセット時に使われるリクエストトークンをReduxで管理する
export const setSession = (session) => ({
type: 'SET_SESSION',
payload: {
id:session.id,
name:session.name
},
})
export const setName = (name) => ({
type: 'SET_NAME',
payload: {
name
}
})
export const setEmail = (email) => ({
type: 'SET_EMAIL',
payload: {
email
}
})
export const setRemember = (remember) => ({
type: 'SET_REMEMBER',
payload: {
remember
}
})
export const setCSRF = (csrf) => ({
type: 'SET_CSRF',
payload: {
csrf
}
})
export const setPrams = (request) => ({
type: 'SET_PARAMS',
payload: {
request
}
})
Reducer
認証関連のReducerは...authenticationsで展開
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'
import * as authentications from './authentications'
const rootReducer = (history) => combineReducers({
...authentications,
router: connectRouter(history)
})
export default rootReducer
...authenticationsで展開するためreducer名は状態名をそのまま使用
export const session = (state = {auth:false,name:null}, action) => {
switch (action.type) {
case 'SET_SESSION':
let session = (action.payload.id===undefined)
? ({auth:false,name:null})
: ({auth:true,name:action.payload.name})
return session
default:
return state
}
}
export const name = (state = null, action) => {
switch (action.type) {
case 'SET_NAME':
return action.payload.name;
default:
return state
}
}
export const email = (state = null, action) => {
switch (action.type) {
case 'SET_EMAIL':
return action.payload.email;
default:
return state
}
}
export const remember = (state = false, action) => {
switch (action.type) {
case 'SET_REMEMBER':
return action.payload.remember;
default:
return state
}
}
export const csrf = (state = '', action) => {
switch (action.type) {
case 'SET_CSRF':
return action.payload.csrf;
default:
return state
}
}
export const params = (state = '', action) => {
switch (action.type) {
case 'SET_PARAMS':
return action.payload.request;
default:
return state
}
}
Route
認証済みか否かでコンテンツを切り分け
状態に適しないページはリダイレクトでデフォルトへ移動させる
import React from 'react'
import { Route, Switch, Redirect } from 'react-router'
import NavBar from '../components/NavBar'
import CardTemplate from '../components/CardTemplate'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { setSession } from '../actions/authentications'
const routes = (session)=> {
return(
<>
<NavBar />
<div className="py-3">
{(()=>{
if(session.id===undefined){
return (
<Switch>
<Route exact path="/login" render={() => <CardTemplate title="Login" content="LoginForm" />} />
<Route exact path="/register" render={() => <CardTemplate title="Register" content="RegisterForm" />} />
<Route exact path="/password/reset" render={() => <CardTemplate title="Reset Password" content="EMailForm" />} />
<Route path="/password/reset/:id" render={(props) => <CardTemplate title="Reset Password" content="ResetForm" params={props.match.params} />} />
<Redirect to="/login" />
</Switch>
)
}
else
{
if(session.email_verified_at===null)
{
return (
<Switch>
<Route exact path="/email/verify" render={() => <CardTemplate title="Verify Your Email Address" content="Verify" />} />
<Redirect to="/email/verify" />
</Switch>
)
}
else
{
return (
<Switch>
<Route exact path="/" render={() => <CardTemplate title="Welcome" content="Welcome" />} />
<Route path="/home" render={() => <CardTemplate title="Dashboard" content="Home" />} />
<Redirect to="/home" />
</Switch>
)
}
}
})()}
</div>
</>
)
}
export default routes
Component
メニュー部分
認証状態によって振り分けるためReduxから認証状態を受け取る
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import {
Container,
Navbar, Nav,
} from 'react-bootstrap'
import LeftMenu from './Menus/LeftMenu'
import RightMenu from './Menus/RightMenu'
const NavBar = (props) => (
<Navbar expand="md" bg="light" className="navbar-laravel">
<Container>
<Navbar.Brand href="/">
Laravel
</Navbar.Brand>
<Navbar.Toggle data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" />
<Navbar.Collapse id="navbarSupportedContent">
<Nav className="mr-auto">
<LeftMenu auth={props.auth} />
</Nav>
<Nav className="ml-auto">
<RightMenu auth={props.auth} />
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
)
NavBar.propTypes = {
auth: PropTypes.bool.isRequired,
}
const mapStateToProps = state => ({
auth: state.session.auth,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(NavBar)
状態によって表示メニューを切り替え
import React from 'react'
import LeftMemberMenu from './LeftMemberMenu'
import LeftGuestMenu from './LeftGuestMenu'
const LeftMenu = (props) => {
if(props.auth)
{
return <LeftMemberMenu />
}
else
{
return <LeftGuestMenu />
}
}
export default LeftMenu
非認証時のメニュー
import React from 'react'
const LeftGuestMenu = () => (
<>
</>
)
認証時のメニュー
import React from 'react'
import { Nav } from 'react-bootstrap'
import { Link } from 'react-router-dom'
const LeftMemberMenu = () => (
<>
<Nav.Item>
<Link to="/home" className="nav-link">Home</Link>
</Nav.Item >
</>
)
状態によって表示メニューを切り替え
import React from 'react'
import { Link } from 'react-router-dom'
import {
Nav, NavDropdown,
Form,
} from 'react-bootstrap'
import RightMemberMenu from './RightMemberMenu'
import RightGuestMenu from './RightGuestMenu'
const RightMenu = (props) => {
if(props.auth)
{
return <RightMemberMenu />
}
else
{
return <RightGuestMenu />
}
}
export default RightMenu
非認証時のメニュー
import React from 'react'
import { Link } from 'react-router-dom'
import { Nav } from 'react-bootstrap'
const RightGuestMenu = () => (
<>
<Nav.Item>
<Link to="/login" className="nav-link">Login</Link>
</Nav.Item>
<Nav.Item>
<Link to="/register" className="nav-link">Register</Link>
</Nav.Item>
</>
)
export default RightGuestMenu
認証時のメニュー
表示するログインユーザー名をReduxから取得する
import React from 'react'
import { Link } from 'react-router-dom'
import { NavDropdown, Form } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CSRFToken from '../Elements/CSRFToken'
class RightMemberMenu extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<NavDropdown title={this.props.name+' 様'}>
<NavDropdown.Item href="/logout" onClick={this.preSubmit}>Logout</NavDropdown.Item>
<Form id="logout-form" action="/logout" method="POST" style={{display:'none'}}>
<CSRFToken />
</Form>
</NavDropdown>
)
}
preSubmit(currentEvent){
currentEvent.preventDefault();
document.getElementById('logout-form').submit();
}
}
RightMemberMenu.propTypes = {
name: PropTypes.string,
}
const mapStateToProps = state => ({
name: state.session.name,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(RightMemberMenu)
コンテンツ部分
メインコンテンツのカード部分のテンプレート
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Container, Row, Col, Card } from 'react-bootstrap'
import ContentSelector from './ContentSelector'
import { setPrams } from '../actions/authentications'
class CardTemplate extends React.Component {
constructor(props){
super(props)
if(props.params!==undefined)
{
props.setPrams(props.params)
}
}
render(){
return (
<Container>
<Row className="justify-content-center">
<Col md="8">
<Card>
<Card.Header>{this.props.title}</Card.Header>
<Card.Body>
<ContentSelector content={this.props.content} />
</Card.Body>
</Card>
</Col>
</Row>
</Container>
)
}
}
CardTemplate.propTypes = {
setPrams: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
})
const mapDispatchToProps = dispatch => ({
setPrams: (request) => dispatch(setPrams(request)),
})
export default connect(mapStateToProps, mapDispatchToProps)(CardTemplate)
ReactRouterから来たパスによって画面振り分け
import React from 'react'
import LoginForm from './Forms/LoginForm'
import RegisterForm from './Forms/RegisterForm'
import EMailForm from './Forms/EMailForm'
import ResetForm from './Forms/ResetForm'
import Home from './Forms/Home'
import Verify from './Forms/Verify'
const ContentSelector = (props) => {
switch(props.content){
case 'LoginForm':
return <LoginForm />
case 'RegisterForm':
return <RegisterForm />
case 'EMailForm':
return <EMailForm />
case 'ResetForm':
return <ResetForm />
case 'Home':
return <Home />
case 'Verify':
return <Verify />
default:
return <div>UnMatch</div>
}
}
export default ContentSelector
ログインフォーム
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import CheckInput from '../Elements/CheckInput'
import LoginButton from '../Elements/LoginButton'
import CSRFToken from '../Elements/CSRFToken'
import { setEmail, setRemember } from '../../actions/authentications'
class LoginForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<Form method="POST" action="/login" id="login-form">
<CSRFToken />
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={true}
/>
<TextInput
identity="password"
controlType="password"
autoComplete="current-password"
label="Password"
/>
<CheckInput
identity="remember"
label="Remember Me"
checked={this.props.remember}
action={this.props.setRemember}
/>
<LoginButton />
</Form>
)
}
}
LoginForm.propTypes = {
email: PropTypes.string,
remember: PropTypes.bool,
setEmail: PropTypes.func.isRequired,
setRemember: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
email: state.email,
remember: state.remember,
})
const mapDispatchToProps = dispatch => ({
setEmail: (email) => dispatch(setEmail(email)),
setRemember: (remember) => dispatch(setRemember(remember)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
新規登録フォーム
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import { setName, setEmail } from '../../actions/authentications'
class LoginForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<Form method="POST" action="/register" id="login-form">
<CSRFToken />
<TextInput
identity="name"
controlType="text"
label="Name"
defaultValue={this.props.name}
action={this.props.setName}
autoFocus={true}
/>
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={false}
/>
<TextInput
identity="password"
controlType="password"
autoComplete="new-password"
label="Password"
/>
<TextInput
identity="password-confirm"
controlType="password"
name="password_confirmation"
autoComplete="new-password"
label="Confirm Password"
/>
<SubmitButton label="Register" />
</Form>
)
}
}
LoginForm.propTypes = {
name: PropTypes.string,
email: PropTypes.string,
setName: PropTypes.func.isRequired,
setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
name: state.name,
email: state.email,
})
const mapDispatchToProps = dispatch => ({
setName: (name) => dispatch(setName(name)),
setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
パスワードリセット申請フォーム
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import SessionAlert from '../Elements/SessionAlert '
import { setEmail } from '../../actions/authentications'
class ResetForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<>
<SessionAlert target="status" />
<Form method="POST" action="/password/email" id="login-form">
<CSRFToken />
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={true}
/>
<SubmitButton label="Send Password Reset Link" />
</Form>
</>
)
}
}
ResetForm.propTypes = {
email: PropTypes.string,
setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
email: state.email,
})
const mapDispatchToProps = dispatch => ({
setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ResetForm)
パスワードリセットフォーム
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import RequestToken from '../Elements/RequestToken'
import SessionAlert from '../Elements/SessionAlert '
import { setEmail } from '../../actions/authentications'
class ResetForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<>
<SessionAlert target="status" />
<Form method="POST" action="/password/reset" id="login-form">
<CSRFToken />
<RequestToken />
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={true}
/>
<TextInput
identity="password"
controlType="password"
autoComplete="new-password"
label="Password"
/>
<TextInput
identity="password-confirm"
controlType="password"
name="password_confirmation"
autoComplete="new-password"
label="Confirm Password"
/>
<SubmitButton label="Reset Password" />
</Form>
</>
)
}
}
ResetForm.propTypes = {
email: PropTypes.string,
setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
email: state.email,
})
const mapDispatchToProps = dispatch => ({
setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ResetForm)
ログイン後のフォーム画面
ステータスセッションがあればば表示する(パスワードリセット時に見られる)
import React from 'react'
import { Container, Row, Col, Card } from 'react-bootstrap'
import StatusAlert from '../Elements/StatusAlert'
const Home = () => (
<>
<SessionAlert target="status" />
You are logged in!
</>
)
export default Home
メール認証前の画面
resentセッションがあればば表示する(メールの再送信に成功した時に見られる)
import React from 'react'
import { Container, Row, Col, Card } from 'react-bootstrap'
import SessionAlert from '../Elements/SessionAlert'
const Verify = () => (
<>
<SessionAlert target="resent" />
Before proceeding, please check your email for a verification link.
If you did not receive the email, <a href="/email/resend">click here to request another</a>.
</>
)
export default Verify
汎用部品
ReduxからCSRFトークンを取得したinput要素
import React from 'react'
import { Form } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
const CSRFToken = (props) => (
<Form.Control type="hidden" name="_token" defaultValue={props.csrf} />
)
CSRFToken.propTypes = {
csrf: PropTypes.string,
}
const mapStateToProps = state => ({
csrf: state.csrf,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(CSRFToken)
パスワードリセット時のリクエストトークンを取得したinput要素
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
const RequestToken = (props) => (
<Form.Control type="hidden" name="token" defaultValue={props.params.id} />
)
RequestToken.propTypes = {
params: PropTypes.object,
}
const mapStateToProps = state => ({
params: state.params,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(RequestToken)
セッションに通知情報があればをグローバル変数で取得してあればアラート表示
import React from 'react'
import { Alert } from 'react-bootstrap'
const SessionAlert = (props) => {
if(laravelSession[props.target]!=='')
{
return (
<Alert variant="success" role="alert">
{
props.target==="resent"
?'A fresh verification link has been sent to your email address.'
:laravelSession[props.target]
}
</Alert>
)
}
else
{
return <></>
}
}
export default SessionAlert
エラー情報をグローバル変数で取得してあればエラーメッセージ表示
import React from 'react'
const FormError = (props) => (
<span className="invalid-feedback" role="alert">
<strong>{props.message}</strong>
</span>
)
export default FormError
テキスト系input要素とラベル、エラーメッセージを組み合わせた部品
import React from 'react'
import { Row, Col, Form } from 'react-bootstrap'
import FormError from './FormError'
class TextInput extends React.Component {
constructor(props){
super(props)
this.state={
error:'',
isInvalid:'',
};
if(laravelErrors[this.props.identity]!==undefined)
{
this.state.error=laravelErrors[this.props.identity][0]
this.state.isInvalid=' is-invalid'
}
}
render(){
let inputProps = {}
inputProps.id=this.props.identity
inputProps.type=this.props.controlType===undefined?'text':this.props.controlType
inputProps.className=this.state.isInvalid
inputProps.name=this.props.name===undefined?this.props.identity:this.props.name
inputProps.autoComplete=this.props.autoComplete===undefined?this.props.identity:this.props.autoComplete
inputProps.required=this.props.required===undefined?true:this.props.required
inputProps.autoFocus=this.props.autoFocus===undefined?false:this.props.autoFocus
if(this.props.controlType!=='password')
{
inputProps.defaultValue=this.props.defaultValue
inputProps.onChange=(text)=>{this.props.action(text.target.value)}
}
return (
<Form.Group>
<Row>
<Form.Label htmlFor={this.props.identity} className="col-md-4 col-form-label text-md-right">{this.props.label}</Form.Label>
<Col md="6">
<Form.Control {...inputProps} />
<FormError message={this.state.error} />
</Col>
</Row>
</Form.Group>
)
}
}
export default TextInput
セッションを記憶ボタンから汎用化したチェックボックス部品
import React from 'react'
import { Row, Col, Form } from 'react-bootstrap'
import FormError from './FormError'
const CheckInput = (props) => (
<Form.Group>
<Row>
<Col md="6" className="offset-md-4">
<div className="form-check">
<Form.Check
id={props.identity}
type="checkbox"
className="form-check-input"
name={props.identity}
checked={props.checked}
onChange={ check => props.action(check.target.checked) }
/>
<Form.Label className="form-check-label" htmlFor={props.identity}>
{props.label}
</Form.Label>
</div>
</Col>
</Row>
</Form.Group>
)
export default CheckInput
各種送信ボタン部品
import React from 'react'
import { Row, Col, Button } from 'react-bootstrap'
const SubmitButton = (props) => (
<Row className="form-group mb-0">
<Col md="6" className="offset-md-4">
<Button type="submit" variant="primary">
{props.label}
</Button>
</Col>
</Row>
)
export default SubmitButton
ログインボタンだけパスワード忘れのリンクが付いているので別定義
import React from 'react'
import { Row, Col, Button } from 'react-bootstrap'
import { Link } from 'react-router-dom'
const LoginButton = () => (
<Row className="form-group mb-0">
<Col md="8" className="offset-md-4">
<Button type="submit" variant="primary">
Login
</Button>
<Link to="/password/reset" className="btn btn-link">
Forgot Your Password?
</Link>
</Col>
</Row>
)
export default LoginButton