【5分で動く】Reactで作るイケてるWEBアプリケーション

react.gif

背景

社内でReactを使ったWEBアプリケーションを開発するにあたって、UIフレームワークの導入から認証までやってくれるようなサンプルが見当たらなかったので、今回作成したものをテンプレっぽくして共有します。
これさえあれば、とりあえず簡単なWEBアプリは作れるのでぜひ活用していただければと思います。

概要

https://github.com/tonio0720/React-App

起動方法

git clone https://github.com/tonio0720/React-App

cd React-App

# react 起動
cd frontend
npm i
npm start

# express 起動
cd backend
npm i
npm start

今回利用したもの

  • React (Frontend Framework)
  • Create React App
  • Ant Design (UI Framework)
  • Axios (HTTP client)
  • echarts (Chart Library)
  • Express (Backend Framework)

解説

Reactアプリ自体はcreate react appを使って作成しました。
ただ色々と拡張する必要があったので、react-app-rewiredcustomize-craを使いました。
UIフレームワークにはAnt Designを使用しています。

バックエンドは認証処理をしたかったのでおまけ程度に書いています。
express-generatorを使って作りました。
認証にはexpress-jwtを使用しています。

空の画面だけでさみしかったので、echartsでダッシュボードっぽくしてみました。
データは僕のQiitaのダッシュボードから持ってきました。(APIではなく直書きです。)
グラフにはechartsを利用しています。

↓で詳細について説明してみます。

Ant Designの導入

Ant DesignはLESSで作られているので、Webpackで読み込める状態にする必要があります。
webpackのバージョン次第では、localIdentNameが云々とエラーが出てしまうので、そちらの対応もしています。

config-overrides.js
const path = require('path');
const {
    override,
    disableEsLint,
    fixBabelImports,
    addLessLoader,
    addWebpackAlias,
} = require('customize-cra');
const theme = require('./src/theme');

const modifyVars = {};
Object.keys(theme).forEach((key) => {
    modifyVars[`@${key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`] = theme[key];
});

const config = {
    webpack: override(
        disableEsLint(),
        addWebpackAlias({
            '@': path.resolve(__dirname, 'src')
        }),
        fixBabelImports('import', {
            libraryName: 'antd',
            libraryDirectory: 'es',
            style: true,
        }),
        // ★ここから
        addLessLoader({
            javascriptEnabled: true,
            modifyVars
        }),
        ((config) => {
            config.module.rules.forEach((rule) => {
                if (!rule.oneOf) {
                    return;
                }
                rule.oneOf.forEach((rule) => {
                    if (!rule.use) {
                        return;
                    }
                    rule.use.forEach((loader) => {
                        if (loader.options && loader.options.localIdentName) {
                            const { localIdentName } = loader.options;
                            delete loader.options.localIdentName;
                            loader.options.modules = { localIdentName };
                        }
                    });
                });
            });

            return config;
        }),
        // ★ここまで
    ),
    devServer: (configFunction) => {
        return (proxy, allowedHost) => {
            const config = configFunction(proxy, allowedHost);
            config.proxy = {
                '/api': {
                    target: 'http://localhost:3030',
                    pathRewrite: { '^/api': '' }
                }
            };
            return config;
        };
    },
};

module.exports = config;

JWT認証

Reactの認証処理はContextProviderを使って実装しています。
ContextProviderは下の階層にプロパティを引き渡すことができます。

ページ遷移の度に、バックエンドの/user/infoというところにリクエストを送り、検証をします。
成功の場合、useridとトークンを保存します。
失敗の場合、ログインページに戻します。

./src/contexts/Auth.js
import React, { useEffect, useState } from 'react';
import useReactRouter from 'use-react-router';

import { getToken, setToken, removeToken, gotoLogin } from '@/utils/auth';
import request from '@/utils/request';

export const AuthContext = React.createContext({});

export const AuthProvider = ({
    children
}) => {
    const { location: { pathname } } = useReactRouter();
    const [isLoggedIn, setIsLoggedIn] = useState(false);
    const [userid, setUserid] = useState(null);

    const checkAuth = () => {
        return request.post('/user/info', {});
    };

    useEffect(() => {
        setIsLoggedIn(false);

        if (pathname === '/login') {
            return;
        }

        const token = getToken();

        if (token) {
            checkAuth().then(({
                token,
                userid
            }) => {
                setToken(token);
                setIsLoggedIn(true);
                setUserid(userid);
            }).catch(() => {
                removeToken();
                gotoLogin();
            });
        } else {
            gotoLogin();
        }
    }, [pathname]);

    return (
        <AuthContext.Provider
            value={{
                isLoggedIn,
                userid
            }}
        >
            {(isLoggedIn || pathname === '/login') && children}
        </AuthContext.Provider>
    );
};

ログインページからは/user/loginにリクエストを送り、usernameとpasswordを検証します。
成功した場合は、tokenが返ってくるのでCookieに保存します。

./src/pages/Login/LoginForm.js
import React, { useState } from 'react';
import useReactRouter from 'use-react-router';
import {
    Form,
    Icon,
    Input,
    Button,
    Checkbox,
    Alert
} from 'antd';

import { setToken } from '@/utils/auth';
import request from '@/utils/request';

import styles from './index.module.less';

const LoginForm = ({
    form
}) => {
    const { history } = useReactRouter();
    const [error, setError] = useState(false);

    const handleSubmit = (e) => {
        e.preventDefault();
        form.validateFields((err, values) => {
            setError(false);
            if (!err) {
                request.post('/user/login', values).then(({ token }) => {
                    setToken(token);
                    history.push('/');
                }).catch(() => {
                    setError(true);
                });
            }
        });
    };

    const { getFieldDecorator } = form;
    return (
        <Form onSubmit={handleSubmit}>
            {error && (
                <Alert
                    description="Password Incorrect."
                    type="error"
                    showIcon
                    style={{ marginBottom: 16 }}
                />
            )}
            <span>username: admin, password: admin</span>
            <Form.Item>
                {getFieldDecorator('username', {
                    rules: [{ required: true, message: 'Please input your username!' }],
                })(
                    <Input
                        autocomplete="off"
                        prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
                        placeholder="Username"
                    />,
                )}
            </Form.Item>
            <Form.Item>
                {getFieldDecorator('password', {
                    rules: [{ required: true, message: 'Please input your Password!' }],
                })(
                    <Input
                        prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
                        type="password"
                        placeholder="Password"
                    />,
                )}
            </Form.Item>
            <Form.Item>
                {getFieldDecorator('remember', {
                    valuePropName: 'checked',
                    initialValue: true,
                })(<Checkbox>Remember me</Checkbox>)}
                <Button
                    type="primary"
                    htmlType="submit"
                    className={styles.loginFormButton}
                >
                    Log in
                </Button>
            </Form.Item>
        </Form>
    );
};

export default Form.create({ name: 'login' })(LoginForm);

終わりに

Reactは便利ですが、部分的なサンプルが多くまとまったものが少ないので不便に感じている人も多いのではないでしょうか。
Reactを始めるきっかけにしてもらえればうれしいです!

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした