20200811のReactに関する記事は13件です。

git pushでnpmパッケージとデモページを更新する(React&TypeScript用のboilerplate付き)

(2020.08. 12更新)
実際にこのboilerplateを使ってnpmパッケージを公開してみました。
https://qiita.com/nariakiiwatani/items/aa6189e86c2bd9b4200f

TL; DR

GitHub: dev-npm-package-react-typescript-boilerplate

概要

ReactコンポーネントやHooksを作ってnpmに公開する際、

  • ソースコードのgit push
  • npmパッケージのpublish
  • デモページの更新

という作業が発生する。
(デモページが必要ない場合もあるかもしれないが、個人的な感覚として、デモページがあるかないかで、導入するモチベーションが5倍くらい変わるのでほんとに単純なものだけでもあった方が良いと思う。)

これらの作業は独立にも行えるが、全て同じタイミングで反映されて欲しいので、手元のコマンド一発で全部できるようにしたい。
かつバージョンの不一致が起きないように、全てのリソースを一箇所に置いておきたい。

今回やることは以下の環境づくり。

  • ライブラリのソースコードをGitHubリポジトリにおく
  • 同じリポジトリにデモページのソースコードを置く
  • リポジトリにgit pushすると以下のGitHub Actinosが実行される
    • デモページのビルドと公開
    • ライブラリのビルドとnpmへのpublish

全部入りのリポジトリはこちら。
GitHub: dev-npm-package-react-typescript-boilerplate

最低限必要な作業は、、、

  • (GitHubに登録)
  • (npmに登録)
  • 自分のGitHubリポジトリの作成
  • npm tokenを生成してリポジトリに登録(自動publish用)
  • package.jsonの編集
    • package名
    • 制作者情報
    • gitリポジトリ情報
  • git push
  • GitHub Pagesの公開ディレクトリ設定

以下、Readmeとかぶるところもあるがその他情報を載せておく。

package.jsonの編集

  • package名
  • 制作者情報
  • gitリポジトリ情報

ライブラリ本体の開発

  • まずyarn run setup:allする。
  • 開発は基本的にsrcフォルダ内で行う。
  • ここに置いたモジュールは、ビルド時にdistフォルダ内にトランスパイルされる。
  • モジュールの追加や削除をした際はindex.jsindex.d.tsも編集しておく。
    • これを忘れると、import時に各モジュールへのパスをいちいち書くことになる。
  • ローカル確認用にビルドする場合はyarn run build
  • git commitは通常通り行って良いが、コミットメッセージに
    • fix: => パッチバージョンアップ
    • feat: => マイナーバージョンアップ
    • perf: => メジャーバージョンアップ
      のいずれかを含まないと、pushしてもライブラリが公開されない。
      コミットメッセージの詳細はsemantic-releaseのドキュメント(英語)を参照。
      git-todos と一緒に使うと良さそう.

※ ライブラリ本体から別のパッケージを使用する場合、package.jsondependenciesではなくpeerDependenciesdevDependenciesに追記するようにする。
こうしないと、このライブラリを使用する環境でたまたま同じパッケージを使用しようとした場合に重複することになり、実行時エラーが起きる可能性がある。

参考: Reactコンポーネントをnpmパッケージとして開発する

関連スクリプト

# ライブラリをビルド。distフォルダに出力される
yarn run build

デモページの作成

普通のReactアプリを開発する感じでOK。
demo/src/components/App.tsxの編集から始めると良い。

関連スクリプト

# デモページをビルド。demo/distフォルダに出力される
yarn run build:demo
# ライブラリをビルドして、デモページをホストするローカルサーバを起動
yarn run dev

リモートリポジトリ設定

npm accountページ -> Auth Tokens で新しいトークンを作ってクリップボードにコピー
GitHubリポジトリのSettings -> Secretsで新しい変数を作成する。

  • NameはNPM_TOKEN
  • Valueはコピーしたトークン

pushしてみる

git push-> GitHub actionsのページから進捗が確認できる

実際にこの仕組みを使って公開しているライブラリがこちらになります。
https://github.com/nariakiiwatani/react-plain-json-editor
https://www.npmjs.com/package/react-plain-json-editor

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsで静的HTMLエクスポートしたアプリをローカルで確認する方法

経緯

Next.jsの静的HTMLエクスポート機能(Static HTML Export)を使うと、サーバーにNode.jsを必要とせずに、クライントのみで実行できる静的HTMLにアプリを出力することできます。

ただし、create-next-appでスキャフォールディングしたプロジェクトに用意されているdevコマンドで起動したアプリはサーバーサイドレンダリング(以下SSR)が有効になっており、静的HTMLのみの確認ができません。

そこでNext.jsで静的HTMLエクスポートしたアプリをローカルで確認する方法を調べました。

確認環境

  • Node.js - 12.4.1
  • Next.js - 9.5.2

設定方法

サンプルプロジェクトを作成します。すでに作成済みの場合は飛ばします。

$ npx create-next-app my-static-site
$ cd my-static-site

serveというパッケージをインストールします。静的ファイルをホスティングするローカルサーバーを建てることができます。
開発でのみ使用するため-Dオプションを指定しています。

$ yarn add -D serve

最後にpackage.jsonのnpmスクリプトを修正します。
静的ファイルを出力するexportコマンドと、ローカルサーバーを立ち上げるserveコマンドを定義します。

next exportではファイルはデフォルトで./outディレクトリに出力されます。
そのため、serve ./outでホスティングするディレクトリを指定しています。

SSRせず、完全に静的HTMLエクスポートしかしない場合、誤認防止の為に不要なコマンド削除しても良いと思います。

  "scripts": {
-   "dev": "next dev",
-   "build": "next build",
-   "start": "next start"
+   "export": "next build && next export",
+   "serve": "yarn export && serve ./out"
  },

実際に下記のコマンドを打つと以下のように表示されます。

$ yarn serve

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[react]nextjs関連のメモ

nextjs関連で学んだことをメモ

現在nextjsの勉強をしているので覚えておいた方が良さそうなところや、
仕組みが不明なとこなどをメモがわりに残しておく。

ページのルーティング

nextjsをなんとなく使っていたが、どういうルールでルーティングができてるのかわからなかった。
nextjsのディレクトリは

pages/
  - index.js
  - sample/
    - index.js
    - show.js

このような構成になっているが
pages/index.jsはlocalhost:3000/,
pages/sample/index.jsはlocalhost:3000/sample/,
pages/sample/show.jsはlocalhost:3000/sample/show/
という風にpagesディレクトリに存在するファイルからエクスポートされたコンポーネントがルーティングになるらしい。

sample/index.js

export default function Sample() {
  return <h1>sample</h1>
}

localhost:3000/sample/にアクセスすると確認できた。

プリレンダリングとは

あらかじめhtmlを生成しておき、後からjavascriptコードが読み込まれる。
(*詳しい方いたら補足して頂けると助かります。)

プリレンダリングの2つの形式

静的生成(Static Generation)

本番にビルドしたタイミングでhtmlを生成しておき、ユーザーのリクエストに応じて生成されたhtmlを再利用して表示する。

サーバサイドレンダリング(SSR)

ユーザーからのリクエスト毎にhtmlが生成される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + TypeScriptで location.state を使う

React(TS)における、コンポーネント間のデータの受け渡しにlocation.stateを使うときの方法。

やりたいこと

bookというstateを表示する Book.tsx と、 そのbook情報を編集する EditBook.tsx があったとします。
このとき Book.tsx 内で編集ボタンをクリックするとbookの情報を EditBook.tsx に渡したい。

方法

Book.tsx
interface BookState {
    book: {
        id: number
        title: string
        author: string
    }
}

class Books extends Component<{}, BookState> {
    state: State {
        book: {
            id: props.id,
            title: props.title,
            author: props.author,
        }
    }

    // bookの内容をEditBookにわたす
    handleEditBook = () => {
        history.push({
            pathname: '/edit_book',
            state: { Book: this.state.book}
        });
    }
}
EditBook.tsx
interface EditBookProps {
    location: any
}

interface EditBookState {
    book: {
        id: number
        title: string
        author: string
    }
}

class EditBook extends Component<EditBookProps, EditBookState> {
    constructor(props: any) {
        super(props);
        this.state = {
            // this.props.location.stateで受け取り
            book: {
                id: this.props.location.state.Book.id,
                title: this.props.location.state.Book.title,
                author: this.props.location.state.Book.author,
            }
        };
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React初心者のための Material-UI 【Dialogの使い方編】

React + Material-UI を勉強しています。今日は「ダイアログ」「モーダル」などと呼ばれる機能の使い方をまとめます。

React のチュートリアルは一通り終えていざ自分で作ろうとするとつまずくことがあるかと思います。なかでも Material-UI の公式ページは便利だけど理解し辛いなあと思うので「使い方」の点からまとめます。ちなみに Dialog ってのはこんな奴ですね。GIFはMaterial-UI のサンプルページです。

2020-08-09 12.25.21.mov.gif

大雑把なプロセスは

  • Dialog のデザインを決める
  • Dialog を含むコンポーネントを作る SampleDialog
  • SampleDialog を開くトリガーを作る
  • state のリフトアップ

となります。

ダイアログのデザインを決める

Dialog を含むコンポーネントのデザインを決めます。直接 <Dialog /> に props を渡せるなら一手間省けますが、今回はひとまわりラッピングされたコンポーネントを想定します。

コンポーネントを作る

ダイアログとトリガーのコンポーネントを作ります。ここでは仮に SampleDialog, SampleCard と名付けます。トリガーのコンポーネントは Card にするつもりなので SampleCard と名付けました。 SampleCard のなかの Button をクリックしてダイアログを開くようにしたいと思います。

図のような構造になります。

trigger-dialog-Page-4.png

SampleDialog を開くトリガーを作る

SampleDialog を開くトリガーを作ります。つまり、 SampleCard のなかに Button を配置します。

class SampleCard extends React.Component {
  render() {
    return (
      <Card>
        <CardContent>Lorem ipsum dolor sit amet</CardContent>
        <CardActions>
          <Button variant="contained" color="primary">
            Open Dialog!
          </Button>
        </CardActions>
      </Card>
    );
  }
}

state のリフトアップ

このセクションがいちばん大変ですね笑。

React では2つ以上のコンポーネントで状態のやりとりをするために共通の親コンポーネントを作り state を一元化するという手法を取ります。これを state のリフトアップと呼んでいます。今回の場合は SampleDialog と SampleCard の間で open という bool 値を共有する必要があります。それを仮に Parent と名付けます。 Parent の state に open を用意します。

trigger-dialog-Copy of Page-4.png

ダイアログを開く

  1. SampleCard 内部の Button がクリックされる
  2. Parent の open が true になる
  3. SampleDialog の props.open が true になりダイアログが開く

というステップがあります。 では、1 から 2 を繋ぐにはどうすればいいのでしょうか?子コンポーネントから親コンポーネントの state をいじるのはできません。そのため、親コンポーネントから渡す props に親コンポーネントの state を変更するメソッドを渡します。たとえば、handleOpen というメソッドを作ります。中身は this.setState({open: true}) です。つまり、 Parent の state を変更するメソッドです。そして、 constructor でメソッドを.bind(this)しておきます(ここは説明不十分だと思います。後日追記する予定)。

trigger-dialog-Copy of Copy of Page-4.png
そして、このメソッドを SampleCard に渡します。
trigger-dialog-Copy of Copy of Copy of Page-4-2.png
Card の props にonClick: handleOpen が入っていますね。これを Button への props に渡すことで 1 から 2 へのステップが実現します。
trigger-dialog-Copy of Page-8-3.png

2から3へのステップは簡単です。Parent から SampleDialog に渡された props.open を<Dialog> に渡します。これでダイアログを開けるようになりました。

ダイアログを閉じる

Dialog は Backdrop(ダイアログの背景、つまり本体より外側の部分)をクリックすると onClose がコールバックされます。onCloseで Parent の open を false にすればダイアログも閉じられます。「ダイアログを開く」でやったのと同じように

  • Parent で メソッド handleClose を定義する
  • Parent から SampleDialog へ props でonClose={handleClose}を渡す

というステップです。

わかりにくかった点

Material-UI のサンプルは Hooks を使ったコードです。それを知らずに読んでいると混乱してしまいました。「俺の知ってる React じゃないぞ???」となりました笑。

おわり

途中で力尽きたので、コードサンプルは載せていません。気が向けば書きます笑。

自分を含めた初心者のための備忘録として書いています。間違い、もっとよい方法などございましたらぜひ教えてください。

参考

https://material-ui.com/api/dialog/
Dialog の props を調べられます。いろいろカスタムできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一から作るモダンフロントエンド環境【Webpack & Babel編編】

どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。

長くなりそうなのでいくつかに分けます。
- 【基本構築編】
- 【Webpack & Babel編】←イマココ
- 【css & 画像バンドル編】(執筆中)
- 【React & React Router編】(執筆中)
- 【Redux編】(執筆中)

Webpack周辺を構築

パッケージ名 説明
webpack 複数のファイルを1つにバンドルして出力してくれるツール。JSファイルを起点にローダーを読み込ませることであらゆるファイルをバンドルできる。
webpack-cli コマンドラインでwebpackを実行するために必要なパッケージ。
webpack-dev-server webpackを使用した開発環境向けのwebサーバー。様々なオプションを設定可能。
html-webpack-plugin webpackから生成したJavaScriptを埋め込んだHTMLテンプレートを生成できる。
$ npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin

webpackの設定を行うためにwebpack.config.jsを作成します。

webpackにはプラグインやローダー(JSファイル以外のファイルをバンドルするための拡張プログラム)を追加することができますがその際の設定もここに記述できます。

webpack.config.js
const path = require('path');
//node.js上でファイルパスを操作できるモジュール

const HtmlWebpackPlugin = require('html-webpack-plugin');
//html-webpack-pluginモジュール

module.exports = {

    mode: 'development',
    //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。

    entry: 'src/index.html',
    //ビルド時に起点となるJSファイルを指定。

    output: {
      filename: 'bundle.js',
      //ビルド時に出力されるファイル名を設定
      path: path.resolve(__dirname, 'dist')
      //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用)
    },

    devServer: {
        port: 8000,
        //ローカルサーバーのポート番号を指定
        open: true
        //webpack-dev-server起動時に自動でブラウザを開く設定
    },

    plugins: [
      new HtmlWebpackPlugin()
      //html-webpack-pluginをwebpackに追加
    ]
}

Babelの設定

Babelとは最新バージョンのECMAScript(Ecma Internationalによって策定されたJavaScriptの標準仕様)を古い型へとコンパイルするツールです。ブラウザによっては最新のECMAScriptをサポートしていない場合があるのでReactなどのモダンJSフレームワークを使用する場合は必須となります。

パッケージ名 説明
@babel/core Babel本体のモジュール
@babel/cli コマンドラインでBabelを実行するために必要なパッケージ。
@babel/preset-env 自動で実行環境に合わせたバージョンでコードをコンパイルする。
babel-loader webpackにBabelを読み込む。

導入するには以下のコマンドを実行

$ npm i @babel/core @babel/cli @babel/preset-env babel-loader

実際にwebpackを実行してみる

webpackを実行するためのスクリプトをpackage.jsonに追記します。
内容は前回の続きからです。

package.json
{
  "name": "react_webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

追記箇所はscriptsプロパティの値です。
これで以下コマンドが使えるようになりました。

$ npm run build

$ npm run start

$ npm run buildがwebpackを実行してアプリケーションをビルドするコマンド。

$ npm run startがwebpack-dev-serverを実行してローカルサーバー上でファイルを実行するコマンドになります。

実際にビルドしてみるとディレクトリ内にdistフォルダが生成され、その中にアプリケーションファイルがあるのが確認できるはずです。ここまで出来たら一旦webpackの基礎構築は完了です。

次はローダーを組み込んでみます。
続く...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一から作るモダンフロントエンド環境【Webpack & Babel編】

どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。

概要

ウェブ開発のデファクトスタンダードとなりつつあるWebpackとモダンJSフレームワークを使用したフロント開発環境構築の手順をまとめました。
自分用のメモも兼ねてるので雑な部分や端折ったりしてる部分があります。

対象者

  • ある程度JSの知識がある人
  • モダンJSフレームワークに興味がある人
  • Webpackを使用した開発環境構築に興味ある人

章節

  • 【基本構築編】
  • 【Webpack & Babel編】←イマココ
  • 【css & sass編】(執筆中)
  • 【React & React Router編】(執筆中)
  • 【Redux編】(執筆中)

Webpack周辺を構築

パッケージ名 説明
webpack 複数のファイルを1つにバンドルして出力してくれるツール。JSファイルを起点にローダーを読み込ませることであらゆるファイルをバンドルできる。
webpack-cli コマンドラインでwebpackを実行するために必要なパッケージ。
webpack-dev-server webpackを使用した開発環境向けのwebサーバー。様々なオプションを設定可能。
html-webpack-plugin webpackから生成したJavaScriptを埋め込んだHTMLテンプレートを生成できる。
html-loader webpackにHTMLを読み込む。

webpack周りの構築を行います。
以下のコマンドを実行してパッケージをダウンロードしてください。

$ npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin html-loader

次にwebpackの設定を行うためにwebpack.config.jsを作成します。

webpackにはプラグインやローダー(JSファイル以外のファイルをバンドルするための拡張プログラム)を追加することができますがその際の設定もここに記述できます。

webpack.config.js
const path = require('path');
//node.js上でファイルパスを操作できるモジュール

const HtmlWebpackPlugin = require('html-webpack-plugin');
//html-webpack-pluginモジュール

const htmlWebpackPlugin = new HtmlWebpackPlugin({
  template: "./src/index.html",
  filename: "./index.html"
});

module.exports = {

    mode: 'development',
    //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。

    entry: './src/index.js',
    //ビルド時に起点となるJSファイルを指定。

    output: {
      filename: 'bundle.js',
      //ビルド時に出力されるファイル名を設定
      path: path.resolve(__dirname, 'dist')
      //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用)
    },

    devServer: {
        port: 8000,
        //ローカルサーバーのポート番号を指定
        open: true
        //webpack-dev-server起動時に自動でブラウザを開く設定
    },

    plugins: [
      htmlWebpackPlugin
      //html-webpack-pluginをwebpackに追加
    ]
}

Babelの設定

Babelとは最新バージョンのECMAScript(Ecma Internationalによって策定されたJavaScriptの標準仕様)を古い型へとコンパイルするツールです。ブラウザによっては最新のECMAScriptをサポートしていない場合があるのでReactなどのモダンJSフレームワークを使用する場合は必須となります。

パッケージ名 説明
@babel/core Babel本体のモジュール
@babel/cli コマンドラインでBabelを実行するために必要なパッケージ。
@babel/preset-env 自動で実行環境に合わせたバージョンでコードをコンパイルする。
babel-loader webpackにBabelを読み込む。

導入するには以下のコマンドを実行

$ npm i -D @babel/core @babel/cli @babel/preset-env babel-loader

Babelの導入に合わせてwebpack.config.jsに設定を追記します。

webpack.config.js
const path = require('path');
//node.js上でファイルパスを操作できるモジュール

const HtmlWebpackPlugin = require('html-webpack-plugin');
//html-webpack-pluginモジュール

const htmlWebpackPlugin = new HtmlWebpackPlugin({
  template: "./src/index.html",
  filename: "./index.html"
});

module.exports = {

    mode: 'development',
    //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。

    entry: './src/index.js',
    //ビルド時に起点となるJSファイルを指定。

    output: {
      filename: 'bundle.js',
      //ビルド時に出力されるファイル名を設定
      path: path.resolve(__dirname, 'dist')
      //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用)
    },

    devServer: {
        port: 8000,
        //ローカルサーバーのポート番号を指定
        open: true
        //webpack-dev-server起動時に自動でブラウザを開く設定
    },

    //Babel追記箇所
    loader: {
      //ここに各種ローダーを追加する
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          //コンパイル対象からnode_modulesを除外
          loader: "babel-loader",
          //ローダーの指定
          query:{
            presets: ["@babel/preset-env"]
          }
      },
      ]
    },

    plugins: [
      htmlWebpackPlugin
      //html-webpack-pluginをwebpackに追加
    ]
}

ビルド用のファイルを作成

Webpackを実行してアプリケーションファイルを出力するためには元となるファイルが必要です。

myAppフォルダのディレクトリ下にsrcフォルダを作成、srcフォルダ内部にindex.htmlファイルとindex.jsファイルを作成して下記のコードをコピペしてください。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>myapp</title>
</head>
<body>
    <h1 id="text"></h1>
</body>
</html>
index.js
const text = document.getElementById('text');
text.innerHTML = "Hello Webpack!"

実際にwebpackを実行してみる

webpackを実行するためのスクリプトをpackage.jsonに追記します。
内容は前回の続きからです。

package.json
{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.10.5",
    "@babel/core": "^7.11.1",
    "@babel/preset-env": "^7.11.0",
    "babel-loader": "^8.1.0",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.3.0",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  }
}

追記箇所はscriptsプロパティの値です。
これで以下コマンドが使えるようになりました。

$ npm run build

$ npm run start

npm run buildがwebpackを実行してアプリケーションをビルドするコマンド。

npm run startがwebpack-dev-serverを実行してローカルサーバー上でファイルを実行するコマンドになります。

実際にビルドしてみるとディレクトリ内にdistフォルダが生成され、その中にアプリケーションファイルがあるのが確認できるはずです。

確認のためwebpack-dev-serverを実行して確認してみましょう。
npm run startを実行してください。

ブラウザ上にHello Webpack!と表示されれば成功しています。

ここまで出来たら一旦webpackの基礎構築は完了です。

次はcssや画像周りを構築していきます。
続く...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『GAFAコーディング面接こんな感じでした』を読んで

GAFAコーディング面接こんな感じでした - yambe2002’s diary を読んで、課題のコードを書いてみる気に。

元記事だと「URL の履歴を保存するデータ構造」がひとつだけど、ふたつ (進む用 + 戻る用) に分割した方が良さそう。

あと、今更 React のチュートリアルを読んだので、練習を兼ねて。

ついでに Bootstrap 5 alpha もチラ見しよう。

Web ブラウザの履歴を管理するようなコード

比較用に 2 種類をつくる。

感想

Bootstrap 5 alpha

React

  • state と props の違い
    • state
      • コンポーネント内部で使うプライベート変数のようなオブジェクト
      • 変更は必ず setState() メソッドを使うこと
    • props
      • 別のコンポーネントから渡される、読み取り専用のオブジェクト
  • React と HTML における属性名の差異
    • class -> className
    • for -> htmlFor
    • readOnly -> readOnly など
  • jQuery との比較
    • JSX とコンポーネントにより、JS 上での CSS セレクターによる要素の指定がほぼ不要に。
    • sate オブジェクトで状態を保持して、それを更新するだけで自動的に DOM 側にも反映できるので簡潔に記述できる。
  • TODO
    • コンポーネントをもっと分割できるのか?
    • フォームの検証はどう書くのが良いのか?
    • 良いコードをもっと読まないと。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで作るandroidアプリ

作ったもの

output.gif

Ionic reactでアプリを作成

ionicを使ったandroidアプリ開発に、Reactが使えると知ったので試してみた。
Reactでどのようにアプリを作っていくのかが知りたかったので、今回はサンプルをエミュレーターで動かすことを目標にした。

$ npm install -g @ionic/cli
$ ionic start myApp sidemenu --type=react
$ cd ./myApp/
$ ionic serve

以下のような画面が表示できれば成功

Screenshot from 2020-06-17 19-13-48.png

android stidio

次に、先ほど作ったアプリをandroidエミュレーターで動かすために準備をする。

ここからandroid studioをダウンロードする。

ダウンロードできたらユーザーガイドに従い、インストールを進める

まず、依存ライブラリをインストール

$  sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386

次に、先ほどダウンロードしたものを展開し、起動

$ sudo mv ~/Downloads/android-studio-ide-193.6626763-linux/android-studio/ /usr/local/
$ cd /usr/local/android-studio/bin
$ ./studio.sh

環境変数の設定

以下の環境変数を .bashrcに追記する。

$ export ANDROID_SDK_ROOT=$HOME/Android/sdk
$ export PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin
$ export PATH=$PATH:$ANDROID_SDK_ROOT/build-tools
$ export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools
$ export PATH=$PATH:$ANDROID_SDK_ROOT/emulator

Android エミュレーターのセットアップ

エミュレーターを使うために、まずはKVMのインストール。
ターミナルに、以下のコマンドをうち、エミュレーターが使えるか確認(出力が0でなければ、エミュレーターを使うことができる)

$ egrep -c '(vmx|svm)' /proc/cpuinfo

エミュレーターを使えることが確認できたらkvmとついでに実機でデバックするためのadbをインストール。

$ sudo apt -y install qemu-kvm adb
$ sudo gpasswd -a "${USER}" kvm

設定を反映するため、再起動を行う。

$ sudo shutdown -r now

ionicアプリのビルド

$ cd myapp/
$ ionic build

android向けにビルド。

$ ionic cap add android

android studioでビルドしたプロジェクトを開く。

$ ionic cap open android

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのRedux内でaxiosを使用した通信をする

はじめに

前々回のReactを使用してWeb画面を作成するでは、reactの簡単なサンプルと共に説明をまとめました。
前回のReduxとReduxToolkitを使用してReact内でデータを管理するでは、reduxのの簡単なサンプルと共に説明をまとめました。
今回はaxiosを使用してREST APIから情報を取得して画面に表示させるサンプルと説明をまとめようと思います。

環境

  • node.js: v12.18.2
  • webpack: 4.44.1
  • React: 16.13.1
  • Redux: 7.2.1
  • axios: 0.19.2

環境作成

前回のReactを使用してWeb画面を作成する前回のReduxとReduxToolkitを使用してReact内でデータを管理するの続きとなります。
環境構築などはそちらを見てください。

axios

REST APIへ通信するためにaxiosというライブラリを使用します。

axiosのインストール

npm install axios

Redux Thunk

axiosは非同期で通信を実行します。しかし、Reduxは非同期の処理をするのは若干手間です。それらを楽にするためにRedux Thunkというミドルウェアを使用します。

Redux Thunkのインストール

npm install redux-thunk

Babel/polyfill

axiosを使用するにあたってpolyfillが必要になります。

Babel/polyfillのインストール

npm install @babel/polyfill --save-dev

Reactでaxiosを使用する

Reactのソースの作成

前回のReduxとReduxToolkitを使用してReact内でデータを管理するの続きなので、ソースも前回のに対して変更していく形にします。

sliceファイルにREST API呼び出しの処理を追加

REST APIの呼び出しと呼び出した後の処理をsliceファイルに追加する。
今回呼び出すREST APIはhttp://localhost:3000/mess_api、ボディは{["message":"testMessage"]}を想定して、このtestMessageをstateのmessに入れて、画面に表示させます。

messageSlice.js
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

export const messageSlice = createSlice({
  name: 'message',
  ~~~ 前回と同じ ~~~
  reducers: {
  ~~~ 前回と同じ ~~~
    // action内にREST APIから取ってきたデータが入っている
    getRequest: (state, action) => {
      // body内の最初のmessageキーの値をstateに入れる
      state.mess = action.payload[0].message;
    }
  },
});

// 今回追加したgetRequestもエクスポートする
export const { sayhello, sayAmount, getRequest} = messageSlice.actions;

// RESTAPIの発行とgetRequestの呼び出しをする
export const getRequestAsync = amount => async dispatch => {
  // ここでREST APIの呼び出しをする
  const response = await axios.get('http://localhost:3000/mess_api').then(response => {
    // 呼び出した結果のボディだけgetRequestに与える(与えるものを返れば上のaction引数に入る内容も変わる)
    dispatch(getRequest(response.data));
  });
};

export default messageSlice.reducer;

storeファイルにthunkを追加

store.js
import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import messageReducer from './slice/messageSlice';

export default configureStore({
  reducer: {
    message: messageReducer,
  },
  middlewares: thunk
});

slice経由でstoreを使用する

コンポーネントでREST APIを発行する際には、sliceで作成したactionを呼び出してstoreを操作します。

App.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { sayhello, sayAmount, getRequestAsync } from '../store/slice/messageSlice';

export function Message() {
  const message = useSelector(state => state.message.mess);
  const dispatch = useDispatch();
  const [messsageAmount, setMesssageAmount] = useState('2');
  ~~~ 前回と同じ ~~~
        {/* sliceで作成したgetRequestAsyncアクションを使用する */}
        <button aria-label="rest get" onClick={() => dispatch(getRequestAsync())}>
          通信
        </button>
        {/* テキストボックスとボタンにインポートしたものを適用する */}
        <input aria-label="set amount" value={messsageAmount} onChange={e => setMesssageAmount(e.target.value)} />
        <button onClick={() => dispatch(sayAmount(messsageAmount))}>
          テキスト変更
        </button>
      </div>
    </div>
  );
}

Reactの実行

前回同様にReactを開発用のサーバで起動してブラウザからアクセスしてください。

"./node_modules/.bin/webpack-dev-server"

通信ボタンを押すとREST APIを発行して、そのボディをstoreに格納することでWeb画面上のテキストも変わります。

REST APIサーバを立てる

REST APIさえ発行できれば何を使用しても良いのですが、手ごろな環境がない場合に簡単なREST APIサーバを立てる方法もまとめます。
node.jsがある環境なのでJSON Serverというライブラリを使用してREST APIを立てます。

JSON Serverのインストール

npm install -g json-server

JSON用ファイルの作成

JSONのエンドポイントとボディを定義したファイルをJSON形式で作成します。
今回はReactからエンドポイント:mess_api、ボディのキー:messageから情報を取っているためそのように記載します。

db.json

{
  "mess_api": [
    { "id": 1, "message": "testMessage" }
  ]
}

JSON Serverの起動

先ほど作成したJSON用ファイルを指定してJSON Serverを起動します。

json-server --watch db.json

コンソールに起動した旨が表示されればREST APIサーバの完成です。

終わりに

ReactのWeb画面作成に関しては、ここで一旦終わりにしようかと思います。React Routerなど他のライブラリもReactにありますが、特に難しくなかったのでまとめなくて良いかなと思います。
REST APIに通信できるようになるとWeb画面で出来ることが増えるため、とても便利だと思います。しかし、Reactでのaxiosの使い方とReduxでのaxiosの使い方が結構違うので苦労しました。
次回はreact-testing-libraryの使い方でもまとめてみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】日本一わかりやすいReact-Redux講座 実践編 #7 「商品を一覧表示しよう」

はじめに

この記事は、トラハック氏(@torahack_)が運営するYoutubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux講座 実践編』の学習備忘録です。

前回まで講座で、Products に対する CRUD のうち、C(:Create), Update(:Update)を実装しました。今回の講座では、CRUD の 一覧表示 R(:Read)、及び D(:Delete)を実装します。

※ 前回記事: 【備忘録】日本一わかりやすいReact-Redux講座 実践編 #6 「useEffectで編集機能を作ろう」

要点

  • Reactでは、親コンポーネントで子コンポーネントをイテレートする形でリスト一覧表示を実装する。
  • Material-UI における <Card/> で、画像+文字列のコンポーネントを実装する。
  • Material-UIにおける <Menu/> で、モーダル開閉を含めたメニューバーを実装する。

完成系イメージ

http://localhost:3000/

image.png

いままで殺風景だったルートページ("/")に、Products を一覧表示します。各Productの登録画像のうち一つがサムネイルとなって表示されます。

image.png

メニューを開くと「編集する」「削除する」ボタンが表示されます、。「編集する」をクリックすると、各Productsの編集ページ("/products/edit/:id")へ遷移します。

「削除する」をクリックすると、

image.png

Productが削除され、残りのProductsで一覧表示がされます。

#7「商品を一覧表示しよう」

reducksファイル

Productsの一覧リスト表示を実装するにあたり、まずreducksファイルを定義します。

operations.js内でDBから Products を一括で取得する関数(fetchProducts())を定義し、取得結果を Store に保存するよう、actions.jsreducers.jsに追記していきます。

また、Store内のProductsをビュー側で参照できるセレクターをselectors.jsに定義していきます。

実装ファイル(reducks)
1.src/reducks/products/operations.js
2.src/reducks/products/actions.js
3.src/reducks/products/reducers.js
4.src/reducks/products/selectors.js
1.src/reducks/products/operations.js
import { fetchProductsAction } from "./actions";
.
.
.
const productsRef = db.collection("products")
.
.
.
export const fetchProducts = () => {
  return async (dispatch) => {
    productsRef.orderBy("updated_at","desc").get()
      .then(snapshots => {
        const productList = []
        snapshots.forEach(snapshot => {
          const product = snapshot.data();
          productList.push(product)
        })
        dispatch(fetchProductsAction(productList));
      })
  }
}

  • orderBy("updated_at","desc") で、DBから取得した結果を更新日時に対する昇順に整理し、fetchProductsActionへ渡します。
2.src/reducks/products/actions.js
export const FETCH_PRODUCTS = "FETCH_PRODUCTS";
export const fetchProductsAction = (products) => {
  return {
    type: "FETCH_PRODUCTS",
    payload: products
  }
}
3.src/reducks/products/reducers.js
import * as Actions from './actions'
import initialState from '../store/initialState'

export const ProductsReducer = (state = initialState.products, action) => {
  switch (action.type) {
    case Actions.FETCH_PRODUCTS:
      return {
        ...state,
        list: [...action.payload]
      };
    default:
      return state
  }
}
  • list: ...action.payloadではなく、list: [...action.payload]とのように、新しい配列として定義することで、配列の格納先メモリが切り替わり、 state が変更されたことを Component 側で検知できるようにしています。
4.src/reducks/products/selectors.js
import {createSelector} from "reselect";

const productsSelector = (state) => state.products;

export const getProducts = createSelector(
  [productsSelector],
  state => state.list
)
  • getProducts()で、Store内の Products を Component 側から参照できるようにします。

ビューファイル

続いてビューファイルを作成します。

Reactにおいて、「DBの中身をリストで一覧表示」を実装するときは、親コンポーネントで子コンポーネントをイテレートするという実装が一般的です。

今回は、親:ProductList.jsx、子:ProductCard.jsxとして、リスト表示を実装します。

image.png

実装ファイル(ビュー関連)
1.src/Router.jsx
2.src/templates/ProductList.jsx
3.src/templates/index.js
4.src/components/Products/ProductCard.jsx
5.src/components/Products/index.js
1.src/Router.jsx
import React from 'react';
import {Route, Switch} from "react-router";
import {ProductEdit,ProductList,Reset,SignIn,SignUp} from "./templates";
import Auth from "./Auth"

const Router = () => {
  return (
    <Switch>
      <Route exact path={"/signup"} component={SignUp} />
      <Route exact path={"/signin"} component={SignIn} />
      <Route exact path={"/signin/reset"} component={Reset} />

      <Auth>
        <Route exact path={"(/)?"} component={ProductList} /> {/* 修正 */}
        <Route path={"/product/edit(/:id)?"} component={ProductEdit} />
      </Auth>
    </Switch>
  );
};

export default Router

ルートページ("/")をProductListテンプレートに設定します。

2.src/templates/ProductList.jsx
import React,{useEffect} from "react";
import {ProductCard} from "../components/Products";
import {useDispatch, useSelector} from "react-redux";
import {fetchProducts} from "../reducks/products/operations"
import {getProducts} from "../reducks/products/selectors"

const ProductList = () => {
  const dispatch = useDispatch();
  const selector = useSelector((state) => state);
  const products = getProducts(selector);

  useEffect (() => {
    dispatch(fetchProducts())
  },[]);

  return (
    <section className="c-section-wrapin">
      <div className="p-grid__row">
        {products.length > 0 &&(
          products.map(product => (
            <ProductCard
              key={product.id} id={product.id} name={product.name}
              images={product.images} price={product.price}
            />
          ))
        )}
      </div>
    </section>
  )
}

export default ProductList
  • useEffect()により、初期レンダー時において、先ほどoperations.jsで定義したfetchProducts()を実行します。その後、const products = getProducts(selector);でDBから取得した商品情報がproductsに格納されます。
  • products.length > 0 &&...で、productsが1つ以上存在する場合のみ、以下の要素をレンダーするよう条件分岐させます。

  • products.map(product => (...))で、mapメソッドを用いて配列の中身をイテレートし、子コンポーネントの<ProductCard />に渡します。イテレートする際は、子コンポーネントには一意の値をkeyとして渡す必要があるため、ユニークな値であるproduct.idkeyに設定します。

3.src/templates/index.js
export {default as Home} from './Home'
export {default as ProductEdit} from './ProductEdit'
export {default as ProductList} from './ProductList' //追記
export {default as Reset} from './Reset'
export {default as SignIn} from './SignIn'
export {default as SignUp} from './SignUp'
  • ProductListをエントリーポイントに追加。
4.src/components/Products/ProductCard.jsx
import React, {useState} from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Typography from '@material-ui/core/Typography';
import NoImage from "../../assets/img/src/no_image.png";
import {push} from "connected-react-router";
import {useDispatch} from "react-redux";
import IconButton from "@material-ui/core/IconButton";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import MoreVertIcon from '@material-ui/icons/MoreVert';
import {deleteProduct} from "../../reducks/products/operations";

const useStyles = makeStyles((theme) => ({
  root: {
    [theme.breakpoints.down("sm")]: {
      margin: 8,
      width: "calc(50% - 16px)"
    },
    [theme.breakpoints.up("sm")]: {
      margin: 16,
      width: "calc(33.333% - 32px)"
    }
  },
  content: {
    display: "flex",
    padding: "16 8",
    textAlign: "left",
    "&:last-child": {
      paddingBottom: 16
    }
  },
  media: {
    height: 0,
    paddingTop: "100%"
  },
  price: {
    color: theme.palette.secondary.main,
    fontSize: 16
  }
}));

const ProductCard = (props) => {
  const classes = useStyles();
  const dispatch = useDispatch();

  const [anchorEl, setAnchorEl] = useState(null);

  const handleClick = (event) => {
    setAnchorEl(event.currentTarget)
  };

  const handleClose = () => {
    setAnchorEl(null)
  };

  const images = (props.images.length > 0) ? props.images : [{path: NoImage}];
  const price = props.price.toLocaleString();

  return (
    <Card className={classes.root}>
      <CardMedia
        className={classes.media}
        image={images[0].path}
        title=""
        onClick={() => dispatch(push("/product/" + props.id))}
      />
      <CardContent className={classes.content}>
        <div onClick={() => dispatch(push("/product/" + props.id))}>
          <Typography color="textSecondary" component="p">
            {props.name}
          </Typography>
          <Typography className={classes.price} component="p">
            ¥{price}
          </Typography>
        </div>
        <IconButton onClick={handleClick}>
          <MoreVertIcon/>
        </IconButton>
        <Menu
          anchorEl={anchorEl}
          keepMounted
          open={Boolean(anchorEl)}
          onClose={handleClose}
        >
          <MenuItem
            onClick={() => {
              dispatch(push("/product/edit/" + props.id))
              handleClose()
            }}
          >
            編集する
          </MenuItem>
          <MenuItem
            onClick={() => {
              dispatch(deleteProduct(props.id))
              handleClose()
            }}
          >
            削除する
          </MenuItem>
        </Menu>
      </CardContent>
    </Card>
  )
}

export default ProductCard

Materila-UIコンポーネントを多く使用しているため、補足します。

image.png

<Card>コンポーネントで、各リストアイテムを表示しています。画像(<CardMedia />)、文字列(<CardContent />)の配置、及びサイズは任意に調整可能です。

<IconButton />をクリックすることでhandleClick()が発火し、setAnchorEl()anchirElに値がセットされることで、モーダルが開きます。

image.png

「編集する」をクリックするとdispatch(deleteProduct(props.id))が発火し、該当するProductsの編集ページへ遷移します。

「削除する」をクリックすると、dispatch(deleteProduct(props.id))が発火し、該当するProductsの削除処理が行われます(deleteProduct()はこのあと実装)

5.src/components/Products/index.js
export {default as ImageArea} from "./ImageArea"
export {default as ImagePreview} from "./ImagePreview"
export {default as ProductCard} from "./ProductCard" //追記
export {default as SetSizeArea} from "./SetSizeArea"
  • ProductCardコンポーネントをエントリーポイントに追加。

ここまでで、一覧表示は一通り完成しました!

最後に、Productsに対するCRUDのDとして、deleteProduct()を実装します。

商品を削除しよう

削除の実装は簡単です。reduckcパターンに則り、以下のファイルに削除処理を定義していきます。

実装ファイル(削除処理)
1.src/reducks/products/operations.js
2.src/reducks/products/actions.js
3.src/reducks/products/reducers.js
1.src/reducks/products/operations.js
import { fetchProductsAction,deleteProductAction } from "./actions";
.
.
.
const productsRef = db.collection("products")

export const deleteProduct = (id) => {
  return async(dispatch, getState) => {
    productsRef.doc(id).delete()
      .then(() => {
        const prevProducts = getState().products.list;
        const nextProducts = prevProducts.filter(product => product.id !== id)
        dispatch(deleteProductAction(nextProducts))
      })
  }
}
.
.
.
  • productsRef.doc(id).delete()とすることで、該当するidの商品情報を、Cloud Firestore上から削除します。
  • getState()により、現時点でのprosuctsを取得します。その後、filterメソッドで該当する商品情報を配列から削除し、deleteProductAction ()に渡します。
2.src/reducks/products/actions.js
export const DELETE_PRODUCT = "DELETE_PRODUCT";
export const deleteProductAction = (products) => {
  return {
    type: "DELETE_PRODUCT",
    payload: products
  }
}
.
.
.
3.src/reducks/products/reducers.js
import * as Actions from './actions'
import initialState from '../store/initialState'

export const ProductsReducer = (state = initialState.products, action) => {
  switch (action.type) {
    // 追記
    case Actions.DELETE_PRODUCT:
      return {
          ...state,
          list: [...action.payload]
      };
    // 追記ここまで
    case Actions.FETCH_PRODUCTS:
      return {
        ...state,
        list: [...action.payload]
      };
    default:
      return state
  }
}

これでProductCard.jsx内で記述したdeleteProducts()関数が正常に動作するようになり、削除機能が実装されます。

動作確認

動作自体は完成系イメージと同様のため割愛します。

deleteProducts()により、Cloud Firestore上でも商品情報の削除が行われているだけ、確認します。、

http://localhost:3000/
image.png

Firebaseコンソール -> Database
image.png

productsが4つ保存されています。メニューから「削除する」をクリックします。

image.png

image.png

商品情報が表示されなくなりました。Firebaseコンソールを確認すると、

image.png

確かに商品情報が削除されています!

さいごに

今回の要点を整理すると、

  • Reactでは、親コンポーネントで子コンポーネントをイテレートする形でリスト一覧表示を実装する。
  • Material-UI における <Card/> で、画像+文字列のコンポーネントを実装する。
  • Material-UIにおける <Menu/> で、モーダル開閉を含めたメニューバーを実装する。

今回は以上です!

このような学習内容を日々呟いていますので、よろしければTwitter(@ddpmntcpbr)のフォローもよろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一から作るモダンフロントエンド環境【基本構築編】

どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。

概要

ウェブ開発のデファクトスタンダードとなりつつあるWebpackとモダンJSフレームワークを使用したフロント開発環境構築の手順をまとめました。
自分用のメモも兼ねてるので雑な部分や端折ったりしてる部分があります。

対象者

  • ある程度JSの知識がある人
  • モダンJSフレームワークに興味がある人
  • Webpackを使用した開発環境構築に興味ある人

章節

  • 【基本構築編】←イマココ
  • 【Webpack & Babel編】
  • 【css & sass編】(執筆中)
  • 【React & React Router編】(執筆中)
  • 【Redux編】(執筆中)

前提

実際のウェブ開発を想定して構築します

事前に導入しておくもの

  • Node.js
  • npm

上記ソフトウェアを使用するので環境に無ければインストールしておきます

$ node -v
v12.16.1

$npm -v
v6.13.4

執筆時点のバージョン情報。

作業環境と下準備

ターミナル or コマンドラインを開き、任意のディレクトリ内で以下コマンドを実行。

mkdir myapp

cd myapp

myAppディレクトリ内で、下記コマンドを実行。

$ npm init -y

すると以下のjsonファイルが生成されます。

packege.json
{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

ここまでが下準備。
続きは以下リンクから。

一から作るモダンフロントエンド環境【Webpack & Babel編】

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-hook-formの基本的な使い方

Reactで入力フォームを開発する際に便利なライブラリであるreact-hook-formの基本的な使い方についてまとめておきます。

公式ドキュメントが丁寧に用意されていますので、網羅的に機能を知りたい方はそちらをご覧ください。

実装手順

基本

以下のようなフォームに値を入力させて、submitする想定で改修を加えていきます。

import React from 'react';

function App() {
    return (
        <form>
            <input name="title" />
            <textarea name="content" />
        </form>
    );
}

export default App;

ライブラリのインストール

ライブラリをインストールします。

npm install react-hook-form

or 

yarn add react-hook-form

refの登録

次にライブラリが各フィールドを参照できるよう、registerメソッドをそれぞれのrefに追加します。
TypeScriptの場合は予め、useFormに対して各フィールドを持った型を渡しておきましょう。

import React from 'react';
import { useForm } from 'react-hook-form';

type Post = {
    title: string;
    content: string;
};

function App() {
    const { register } = useForm<Post>();
    return (
        <form>
            <input
                name="title"
                ref={register({ required: true, maxLength: 30 })}
            />
            <textarea name="content" ref={register} />
        </form>
    );
}

export default App;

react-hook-formには基本的なバリデーションも用意されており、このregisterを追加する際に設定することが可能です。

利用できるバリデーションについては以下を参照ください。

https://react-hook-form.com/get-started/#Applyvalidation

バリデーションエラーについて

設定したバリデーションでエラーが発生した場合はreact-hook-formが用意しているerrorオブジェクトを利用してハンドリングすることができます。

import React from 'react';
import { useForm } from 'react-hook-form';

type Post = {
    title: string;
    content: string;
};

function App() {
    const { register, errors } = useForm<Post>();
    return (
        <form>
            <input
                name="title"
                ref={register({ required: true, maxLength: 30 })}
            />
            {errors.title && <span>タイトルは必須です。</span>}
            <textarea name="content" ref={register} />
        </form>
    );
}

export default App;

submit

そして、formタグに対して、コールバックを渡したhandleSubmitを設定します。

import React from 'react';
import { useForm } from 'react-hook-form';

type Post = {
    title: string;
    content: string;
};

function App() {
    const { register, handleSubmit, errors } = useForm<Post>();
    const onSubmit = (data: Post) => {
        console.log(data);
    };
    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <input
                name="title"
                ref={register({ required: true, maxLength: 30 })}
            />
            {errors.title && <span>タイトルは必須です。</span>}
            <textarea name="content" ref={register} />
        </form>
    );
}

export default App;

これでフォームが送信アクションが発生した際に、入力した値がonSubmitに渡されるようになります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む