LoginSignup
3
2

More than 3 years have passed since last update.

create-react-app のメニュー表示と認証・入力内容での表示制御

Posted at

概要

create-react-app を利用した基本的なSPAの実装サンプルの共有です。
メニュー表示と認証、イベント処理などを網羅的に扱っているサンプルがなかったので後世のために記事にしてみます。アプリケーションの形態としては管理機能が近いかと。

アプリケーション外観

list.png

ソースコードはGitHubの react-trial にて公開しているので参考にしてください。
なお、ReactやMateril-UIの扱いに至らぬところがあれば指摘をいただけるとありがたいです。

この記事で触れる事項

  • 認証の前後でのメニュー表示制御、リダイレクト制御
  • セッション管理
  • 入力内容に応じて関連する項目の表示を変更する
  • ヘッダー固定のテーブルの実現方法
  • Materil-UIの基本的な使い方

この記事で触れない事項

Reactに固有の話ではない一般的な事項なのでこの記事では触れません。

  • 認証処理
  • APIリクエストの実行とレスポンスの処理

前提条件

  • Reactのバージョン 16.8.6
  • 認証状態、セッション管理に Context APIlocalStorage を利用しています
  • ルーティングは react-router 5.0.0 を利用しています
  • ES6以降の記法でコードは記述しています

認証の前後でのメニュー表示制御、リダイレクト制御

認証前の表示

認証前.png

認証後の表示

butons.png

解説

認証されているかどうかで表示するメニューとルーティングを以下のようなコードで切り替えています。

const Routing = (props) => {
  return (
    <BrowserRouter>
      <div>
        <ul className="flex-container" style={{paddingLeft: '5px'}}>
          <li>
            <div className="side-menu">
              { props.authenticated ? <AuthenticatedMenus /> : <NotAuthenticatedMenus /> }
            </div>
          </li>

          <li className="right-justified-item" style={{width: '100%'}}>
            { props.authenticated ? <AuthenticatedRouting /> : <NotAuthenticatedRouting /> }
          </li>
        </ul>
      </div>
    </BrowserRouter>
  );
};

メニュー表示とルーティングの切替

認証前は以下のようなルーティングで /login 以外に遷移しようとすると /login にリダイレクトします。

const NotAuthenticatedRouting = () => {
  return (
    <Switch>
      <Route path="/login" component={LoginForm} />
      <Route path='*'
        render={() => (
          <Redirect to="/login" />
        )}
      />
    </Switch>
  );
};

認証後は以下のルーティングが適用され、表示されるメニューに対応する画面が表示されるようになります。

const AuthenticatedRouting = () => {
  return (
    <Switch>
      <Route path="/" exact component={HomeForm} />
      <Route path="/button" exact component={ButtonForm} />
      <Route path="/list" exact component={ListForm} />
      <Route path="/event" exact component={EventForm} />
      <Route path='*'
        render={() => (
          <Redirect to="/" />
        )}
      />
    </Switch>
  );
};

利用ユーザーの役割、権限などによって利用できるメニューが異なるようなケースではこの実装ではつらくなるので Route コンポーネントを継承・拡張してその中で利用可能なメニューかを判定するような設計、実装にした方がよいかと。

認証・セッション管理

Context API と独自のグローバルなセッション管理オブジェクトを組み合わせて認証状態・セッション管理を行っています。

export const SessionContext = React.createContext({
  authenticated: false,
  login: () => {},
  logout: () => {}
});
class SessionState {
  constructor() {
    this.session = {};
    this.authenticated = false;

    this.sessionKey = process.env.REACT_APP_SESSION_KEY || '';
    const session = JSON.parse(localStorage.getItem(this.sessionKey));
    if (session) {
      this.session = session;
      this.authenticated = true;
    }
  }

  login() {
    this.session = {
      // 中略
    };
    localStorage.setItem(this.sessionKey, JSON.stringify(this.session));
    this.authenticated = true;
  }

  logout() {
    localStorage.removeItem(this.sessionKey);
    this.session = {};
    this.authenticated = false;
  }
}

export const sessionState = new SessionState();

React実装のメニュー制御、表示制御は Context API で足りますが、WEBブラウザを終了させたり、リロードした場合にはセッション状態は消失して再度の認証が必要になってしまいます。
メモリ外にセッション状態を保持するために localStorage を利用してJavaScriptがロードされた際に以前のセッションを再現させるようにしています。
SessionState のJavaScriptがロードされると new SessionState() が実行され、インスタンス生成時に localStorage に保存されたセッションを確認し、存在する場合はメモリ上に展開します。

App コンポーネントが生成される際に SessionState の認証状態が反映され、以降のメニュー表示やルーティングの制御に伝播されていきます。

this.state = {
  authenticated: sessionState.authenticated
};

localStorageのセッションをReactの実装に反映する

入力内容に応じて関連する項目の表示を変更する

セレクトボックスの内容に応じてボタンの表示を変更する処理とチェックボックスのチェックの有無でテキスト入力欄の表示・非表示を切り替える処理の紹介です。
実装の全容はこちら

初期状態

イベント_初期状態.png

変更後

イベント_変更状態.png

セレクトボックスの選択内容でボタンの外観を変更する

Material-UI の Select コンポーネントを利用しています。
セレクトボックスの onChange イベントに親コンポーネントの onChangeSelect イベントをバインドして親コンポーネントの state を変更するようにしています。

<Select
  value={this.state.buttonColor}
  onChange={(e) => this.onChangeSelect(e, 'buttonColor')}
  style={{minWidth: '150px'}}
>
  <MenuItem value={'default'}>default</MenuItem>
  <MenuItem value={'primary'}>primary</MenuItem>
  <MenuItem value={'secondary'}>secondary</MenuItem>
</Select>
onChangeSelect(e, key) {
  this.setState({[key]: e.target.value});
}

Material-UI の Button のプロパティに親コンポーネントの state をバインドして変更内容を反映しています。

<Button
  variant={this.state.buttonVariant}
  color={this.state.buttonColor}
>
  {this.state.buttonColor}
</Button>

ポイントになるのは子コンポーネントのイベントには親コンポーネントのイベントをバインドする必要がある点です。
親コンポーネントが setState 関数を経由して state を変更しないと子コンポーネントにその変化が反映されず、再描画されません。

チェックボックスをチェックしたらテキスト入力欄を表示する

Material-UI の Checkbox を利用しています。

<Checkbox
  checked={this.state.showEdit}
  onChange={(e) => this.onChangeCheckbox(e, 'showEdit')}
/>

CheckboxonChange イベントにバインドしている親コンポーネントのイベント実装は以下。

onChangeCheckbox(e, key) {
  this.setState({[key]: e.target.checked});
}

そしてテキスト入力欄の TextField の実装は以下のようになります。
this.state.showEdit = true すなわち、チェックボックスがチェックされている場合だけ描画されます。

{this.state.showEdit ? <TextField label="名前" value={this.state.edit} /> : null}

ヘッダー固定のテーブルの実現方法

react-table をはじめ、いくつかテーブルコンポーネントを触ってみたのですがデザインの良さ、シンプルさから、Materil-UIの Table が一番しっくりきました。
ただ、 Table はヘッダー行の固定が実現されておらず こちらの書き込み を参考にCSSで補正して実現しています。

.sticky-head {
  background-color: #fff;
  position: sticky;
  top: 0;
}
<div style={{height: '400px', overflow: 'scroll'}}>
  <Table>
    <TableHead>
      <TableRow>
        <TableCell className="sticky-head">id</TableCell>
        <TableCell className="sticky-head">label</TableCell>
        <TableCell className="sticky-head">action</TableCell>
      </TableRow>
    </TableHead>
    // 後略

実装の全容はこちら

スクロール挙動

sticky-table.gif

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2