23
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-10

react.gif

背景

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

概要

起動方法

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を始めるきっかけにしてもらえればうれしいです!

23
21
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
23
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?