20200301のReactに関する記事は6件です。

Rails+Reactアプリを1つのdynoでデプロイする

railsAPIモードとReactで記事投稿アプリを作りました。API側とクライアント側を別々にHerokuにデプロイすると、SPAとは思えないほどアプリの動きが重くなったので、1つのdynoでデプロイし直した時のことをメモします。

参考になった記事

A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku | Heroku
ReactJS + Ruby on Rails API + Heroku App - Medium
React + RailsのアプリをHerokuで動かす方法 - Qiita

はじめに

ディレクトリ構成

project
      ┝ app
      ┝ bin
      ┝ config
      ┝ db
      ┝ front <--reactのディレクトリ
    ┝ lib
      ┝ log
      ┝ public
      ┝ storage
      ┝ test
      ┝ tmp
      ┗ vendor

アプリ名をprojectとしています
ディレクトリ構成はrailsアプリの中にreactアプリがあるという状態です
アプリのルートディレクトリにGemfileがないと後々buildpackを用いてデプロイする時にエラーが出るのでこのような構成にしました
参考:Heroku:Buildpackエラーでハマった

.envファイル生成

開発環境でのAPI側のURLを.envファイルに書きます
frontディレクトリ下で.envファイルを作成し、以下を記述します

project/front/.env
REACT_APP_SERVER_URL=http://localhost:3001

URLを取得する時はprocess.env.REACT_APP_SERVER_URLで取得できます
axiosでHTTP通信する場合は以下のように書けます

project/front/Component/UserIndex.js
axios
      .get(`${process.env.REACT_APP_SERVER_URL}/api/users`, headers)

Foremanの導入

Foremanは複数のプロセスをまとめて管理できるツールです
APIとクライアント側を一つのコマンドで動かせるので便利です
Gemfileに以下を記述し、bundle install --path vendor/bundleします

project/Gemfile
gem 'foreman'

次にProcfile.devを作成し、以下を記述します

project/Procfile.dev
frontend: exec /usr/bin/env PORT=3000 sh -c 'cd front && yarn start'
backend: exec /usr/bin/env PORT=3001 sh -c 'bundle exec rails s'

ここでforeman start -f Procfile.devを実行し、http://localhost:3000にアクセスすると確認できます

ただ、lib/tasks下にstart.rakeファイルを作成し、以下を記述すると、rake startとコマンドに打つだけでブラウザで確認できます

project/lib/tasks/start.rake
namespace :start do
    desc 'Start dev server'
    task :development do
      exec 'foreman start -f Procfile.dev'
    end

    desc 'Start production server'
    task :production do
      exec 'NPM_CONFIG_PRODUCTION=true yarn heroku-postbuild && foreman start'
    end  
end
task :start => 'start:development'

また、rake start:productionコマンドでデプロイ前にローカルで簡単に本番環境のテストを行うことができるように記述しています

Herokuにデプロイ

package.json作成

まず、projectディレクトリ下にpackage.jsonファイルをnpm initコマンドで作成します

project/package.json
{
  "name": "project",
  "version": "1.0.0",
  "description": "You can post articles using this application.",
  "main": "index.js",
  "scripts": {
    "build": "cd front && npm install && npm run build && cd ..",
    "deploy": "cp -a front/build/. public/",
    "heroku-postbuild": "npm run build && npm run deploy && echo 'Client built!'"
  }
}

Herokuはheroku-postbuildに定義されていることを実行します。ここでは、frontディレクトリでnpm install, npm run buildを実行し、front/buildの内容をpublic/にコピーします

Herokuアプリ作成

heroku create アプリ名でHerokuアプリを生成します
そして、Herokuのbuildpackを追加します

$ heroku buildpacks:add heroku/nodejs --index 1
$ heroku buildpacks:add heroku/ruby --index 2

heroku buildpacksで確認して、以下のような順番になっていればOKです

=== project Buildpack URLs
1. heroku/nodejs
2. heroku/ruby

Procfile作成

次にProcfileを作成し、Herokuがrailsアプリを起動するためのコマンドを記述します

project/Procfile
web: bundle exec rails s

Procfileを作成したため、ここでrake start:productionコマンドによりローカルで本番環境のテストを行うことができます

本番環境用のエンドポイント設定

Reactアプリが本番環境でのAPI側のURLを参照できるように.env.productionファイルを作成します

project/front/.env.production
REACT_APP_SERVER_URL=https://project.herokuapp.com

また、クライアント側からのアクセスを許可するため、cors.rbのoriginに追記します

project/config/initializers/cors.rb
origins 'http://localhost:3000', 'https://project.herokuapp.com/api'

本番環境では、APIはhttps://project.herokuapp.com/api/で、クライアント側はhttps://project.herokuapp.com/で起動するようになっています

.gitignoreに追記

次に.gitignoreに以下を追記します

project/.gitignore
/public
/vendor/bundle

Postgre.SQLを使用するための設定

本番環境のDBにPostgre.SQLを使用するための設定を行います
Gemfileに以下を追記し、bundle install --without productionを実行します

project/Gemfile
group :production do
  gem 'pg', '>= 0.18', '< 2.0'
end

次にdatabase.ymlを以下のように書き換えます

project/config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  database: project_production
  username: project
  password: <%= ENV['PROJECT_DATABASE_PASSWORD'] %>

デプロイ

これでデプロイする準備が整いました
Herokuにpushしてください

git add .
git commit -m 'ready for first push to heroku'
git push heroku master

heroku run rails db:migrateして終了です

React Routerを使っている場合

React Routerを機能させるために以下の設定をします

fallback_index_htmlメソッド定義

ApplicationControllerにfallback_index_htmlメソッドを追加します

project/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def fallback_index_html
    render :file => 'public/index.html'
  end
end

ApplicationControllerがActionController::APIを継承している場合はhtmlファイルを返すことができないため、以下のように追記します

project/app/controllers/application_controller.rb
class ApplicationController < ActionController::API
    include ActionController::MimeResponds
    def fallback_index_html
        respond_to do |format|
            format.html { render body: Rails.root.join('public/index.html').read }
        end
    end
end

route.rbでfallback_index_html

そして、routes.rbにも以下を追記します
railsのルーティングについて書いている部分より下に追記してください
ここでfallback_index_htmlメソッドによりpublic/index.htmlが返されるため、React Routerを参照することができます

project/config/routes.rb
Rails.application.routes.draw do
  get '*path', to: "application#fallback_index_html", constraints: ->(request) do
    !request.xhr? && request.format.html?
  end
end

これでpushすると、React Routerによるルーティングが反映されると思います
Herokuにデプロイするまでの手順は以上です

最後に

参考にしたサイトを見ながらでも結構エラーで詰まるところがあり、デプロイするまでに時間がかかりました。しかし、APIとクライアントを別々でデプロイするよりもアプリの動きは軽くなったのでよかったです。
この記事について間違っているとこなどあればご指摘いただけると嬉しいです。

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

Reactを初めて触ってみる。(ついでにFirebaseHostingにデプロイしてみる)

普段は業務でAngularばっかり使ってますが、社内のアプリ開発コンテストでReactを使うことになりました。
徐々に勉強していこうと思いますが、まずは環境構築について書いてみます。

Getting Started

公式で色々書いてありますが、ローカル環境の構築方法は以下に書いてありました。
https://ja.reactjs.org/tutorial/tutorial.html#setup-option-2-local-development-environment

プロジェクトの作成

npx create-react-app my-app
package.json
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.4.1",
    "@testing-library/user-event": "^7.2.1",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "react-scripts": "3.4.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

プロジェクトの起動

  • npm startで起動します
npm start
  • 起動に成功すると、以下のような表示が出ます
Compiled successfully!

You can now view my-app in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://10.0.2.15:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

→ Productionビルドしたかったらnpm run build叩いてねって書いてありますね。

  • ブラウザでアクセスします。
    ※VM(192.168.33.10)上で動かしています

image.png

テンプレートプロジェクトの構成について

  • プロジェクトルート
[vagrant@localhost /samba/my-app (master)]$ ll
合計 628
-rw-r--r--.    1 vagrant docker   2891  2月 26 22:39 README.md
drwxr-xr-x. 1022 vagrant docker  32768  2月 26 22:42 node_modules
-rw-r--r--.    1 vagrant docker 581143  2月 26 22:40 package-lock.json
-rw-r--r--.    1 vagrant docker    742  2月 26 22:40 package.json
drwxr-xr-x.    2 vagrant docker    120  2月 26 22:39 public
drwxr-xr-x.    2 vagrant docker    148  2月 26 22:39 src
  • public ディレクトリ ・・・ 静的ファイル置き場
[vagrant@localhost /samba/my-app (master)]$ ll public
合計 36
-rw-r--r--. 1 vagrant docker 3150  2月 26 22:39 favicon.ico
-rw-r--r--. 1 vagrant docker 1721  2月 26 22:39 index.html
-rw-r--r--. 1 vagrant docker 5347  2月 26 22:39 logo192.png
-rw-r--r--. 1 vagrant docker 9664  2月 26 22:39 logo512.png
-rw-r--r--. 1 vagrant docker  492  2月 26 22:39 manifest.json
-rw-r--r--. 1 vagrant docker   67  2月 26 22:39 robots.txt
  • srcディレクトリ ・・・ コード置き場
[vagrant@localhost /samba/my-app (master)]$ ll src
合計 36
-rw-r--r--. 1 vagrant docker  564  2月 26 22:39 App.css
-rw-r--r--. 1 vagrant docker  555  2月 26 22:39 App.js
-rw-r--r--. 1 vagrant docker  280  2月 26 22:39 App.test.js
-rw-r--r--. 1 vagrant docker  366  2月 26 22:39 index.css
-rw-r--r--. 1 vagrant docker  452  2月 26 22:39 index.js
-rw-r--r--. 1 vagrant docker 2671  2月 26 22:39 logo.svg
-rw-r--r--. 1 vagrant docker 5085  2月 26 22:39 serviceWorker.js
-rw-r--r--. 1 vagrant docker  255  2月 26 22:39 setupTests.js

修正してみる

App.jsの文章をHello Reactに変えてみます

src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Hello React
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

 ↓ 保存すると即座にリロードされます

image.png

Firebaseを使って公開してみる

基本的には以前に執筆したAngularのホスティングと同じなので、異なる点だけ書いておきます。

ReactプロジェクトをProductionビルド

  • npm startした際に表示される通り、npm run buildだけでproductionビルドとなります
npm run build

 ↓

[vagrant@localhost /samba/my-app (master *)]$ npm run build

> my-app@0.1.0 build /samba/my-app
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  39.38 KB  build/static/js/2.bc5b117a.chunk.js
  771 B     build/static/js/runtime-main.aaf88d20.js
  605 B     build/static/js/main.63c75bea.chunk.js
  556 B     build/static/css/main.d1b05096.chunk.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Find out more about deployment here:

  bit.ly/CRA-deploy

buildディレクトリに出力されました。

Firebase Hostingへのデプロイ

  • 公開するディレクトリはbuildに変更します
? What do you want to use as your public directory? (public) build
  • SPAの設定はyを選びます
? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) y
  • index.htmlは上書きしません
? File build/index.html already exists. Overwrite? (y/N) N

デプロイ

firebase deploy

アクセスしてみる

image.png

最後に

Reactについては全然わかってないですが、とりあえず環境の構築とFirebaseでの公開はできました。
SPAは簡単に公開できて良いですね。

徐々にReactについても勉強していこうと思います。

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

[React]動的にコンポーネントを表示する

URL の変更に合わせてコンポーネントを切り替えたくて、あれこれやってみたのでメモ。

react: 16.13.0
react-router-dom: 5.1.2

Route

// url から Component を決めて表示したいので slug を設定
<Route path='/:slug' component={MyComponent} />

Component

import React, { FC } from 'react';
import { useParams } from 'react-router-dom';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import NotFound from './NotFound';

const MyComponent: FC = () => {
  // Route で設定したパラメータを取得
  const { slug } = useParams();

  // URL に合わせてコンポーネントを設定。マッチしなかった場合用に NotFound を用意。
  const components: { [key: string]: FC } = {
    'component-a': ComponentA,
    'component-b': ComponentB,
    'not-found': NotFound,
  };

  // slug が存在してマッチすれば上で指定した Component を、そうでなければ NotFound をセット。
  const ComponentName = (slug && components[slug]) || components['not-found'];

  return <ComponentName />;
};

export default MyComponent;

これで、 URL に対して components で対応させたコンポーネントが表示される。

これよりもこっちの方がいいよ!とかあれば教えてもらえると嬉しいです!

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

Reactの最強状態管理ツールreact-queryがメジャーリリースされたぞ!

react-query とは?

react-queryとは 2 月 18 日にメジャーリリースされたばかりの React プロジェクトにおける状態管理用のモジュールです。
ネット上に情報がまだ少ないため、一部抜粋しながら使い方と魅力についてまとめていきます。

時間があるときに追加していくのでまだ準備中が多いです。READMEがめちゃくちゃ分かりやすいのでそちらを見たほうが断然早いです。

tannerlinsley/react-query
https://github.com/tannerlinsley/react-query

基本的な機能としては、Hooksを用いた

  • データの取得(サーバの実装は REST でも GraphQL でも何でも ok)
  • 自動キャッシュ
  • 自動再取得
  • データの反映 → データの再取得
  • ページネーションにも対応
  • リクエストキャンセル

です。
基本的にクエリに任意のqueryKeyを与え、取得したデータをqueryKeyごとにglobal stateに自動でキャッシュしてくれます。

僕が考えるこのモジュールの最大のメリットは、データの取得の API を叩くロジックを一定のルールに沿ってラッピングするため、コードの可読性が上がるという点にあると思います。また、Suspenceを用いた再取得がとてもわかりやすく書けるので最高です。

以下はtodoId = 1todoを取得する例ですが、API を叩く関数では promise を返し、使う側はkeytodoIdqueryKeyとして配列で渡すだけで、loading, error, successの状態やerrorが発生しているかどうかのbooleanを簡単に取得できます。

const todoId = 1;
const { status, data, error } = useQuery(["todos", "todoId"], fetchTodoList);

const fetchTodoList = (key, todoId) => {
  return axios.get(`/todos/${todoId}`).then(res => res.data);
};

react-query のインストール

以下のコマンドで追加できます。

yarn add react-query

DefinitelyTyped が公開されていますが、現在(2020/3/1)はまだ古いバージョンのため、typescriptで利用するのは厳しいようです。

yarn add @types/react-query

tannerlinsley/react-query
https://github.com/tannerlinsley/react-query

queryKey とは

queryKeyはこのモジュールの最重要ポイントです。string もしくはstring, numberの値とそれらを含むobjectarrayで示されます。
キャッシュの管理を行うために必須な値であり、雰囲気としてはGraphQLqueryに似ています。
このモジュールの強みはqueryKeyが以下のように柔軟に設定できることにあります。

String-Only Query Keys

文字列一つをqueryKeyとするときはこのように書きます。

useQuery('todos', ...)
// queryKey === ['todos']

右側のコメントがモジュール内で利用されるqueryKeyで、配列型に変換されるようになっています。

Array Keys

もし、その他の情報やqueryFunction(第 2 引数に渡す fetch する関数)がパラメータによって一意に決まる場合は以下のような書き方になります。

useQuery(['todos', { status: 'done' }], ...]
// queryKey === ['todos', { status: 'done' }

const todoId = 5
useQuery(['todos', todoId], ...]
// queryKey === ['todos', 5]

useQuery(['todos', todoId, { preview: true }], ...]
// queryKey === ['todos, 5, { preview: 'true' } ]

このときqueryFunctionの引数はuseQueryの第一引数と対応しています。

// 例の3つ目
// useQuery(['todos', todoId, { preview: true }], ...] // queryKey === ['todos, 5, { preview: 'true' } ]
const fetchTodoList = (key, todoId, { preview }) => {
 ...
}

queryKey の並び順による影響

queryKeyの並び順は

  • object 内は考慮されない
  • array 内は考慮される

です。

// 以下は全て同じ扱い
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)

// 以下は全て異なる扱い
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)

複数クエリがある場合の挙動

同一ページで複数のクエリを叩くパターンとして、

  • 並列クエリ...複数のクエリを同時に叩く
  • 直列クエリ...あるクエリの結果を元にクエリを叩く

があります。

並列クエリの場合にはクエリを複数の書くだけで簡単に実装できます。
後者の場合はこれまではメソッドチェーンやコールバック、Suspenceを駆使して実装していたでしょう。
しかし、react-query ではとても簡潔に書くことができます。

ここでは、以下の例をについて見ていきましょう。

emailからユーザ情報を取得し、取得したユーザのidからユーザに紐づくprojects一覧を取得する

従来だとこのような感じでしょうか

cosnt fetchProjects = async () => {
  const user = await getUserByEmail(email)
  const projects = await getProjectsByUser(user.id)
  setProjects(projects)
}

useEffect(() => {
  fetchProjects()
}, [])

パターン 1: queryKey に falsy な値を渡す

1 つ目のパターンはqueryKeyfalsy な値を渡すパターンです。

// react-queryを用いる際はdataを別の変数名で定義します。
const { data: user } = useQuery(["user", { email }], getUserByEmail);

const { data: projects } = useQuery(
  // userがnullでなくなったらqueryFunctionが実行されます
  user && ["projects", { userId: user.id }],
  getProjectsByUser
);

とても簡単で見やすいですね!

パターン 2: queryKey の Item に falsy な値を渡す

パターン 1 ではqueryKey自体がfalsyでしたが、queryItemfalsyな場合でも同様の効果が得られます。
これによって、複数の依存があるクエリの場合でもネストが無いためとても読みやすいですね!

const { data: user } = useQuery(["user", { email }]);

const { data: projects } = useQuery(
  ["projects", user && user.id],
  getProjectsByUser
);

また、コンパイラが対応している場合にはOptional Chainingで書くことも可能です!

const { data: projects } = useQuery(["projects", user?.id], getProjectsByUser);

パターン3: queryFunctionでの例外検知

const { data: user } = useQuery(['user', { email }])

const { data: projects } = useQuery(
  () => ['projects', { userId: user.id }]
)

queryKeyFunctionを渡すこともできます。
上記のコードでは、usernullのときにuser.idへアクセスしようとすると例外が発生します。
このような場合、例外が発生しなくなるまで再取得を試みてくれます。
一番Suspenceを使うときに近いですね。

API

useQuery

準備中...

usePaginatedQuery

準備中...

useInfiniteQuery

準備中...

useMutation

準備中...

queryCache

準備中...

queryCache.prefetchQuery

準備中...

queryCache.getQueryData

準備中...

queryCache.setQueryData

準備中...

queryCache.refetchQueries

準備中...

queryCache.removeQueries

準備中...

queryCache.getQuery

準備中...

queryCache.subscribe

準備中...

queryCache.clear

準備中...

useIsFetching

準備中...

ReactQueryConfigProvider

準備中...

setConsole

準備中...

おわりに

サーバがREST APIで実装されている場合、これを使うことですごく書きやすくなりそうな雰囲気を感じます。
GraphQLにも対応はしていますが、Apollo Clientの方が純粋に特化しているのでそちらを使ったほうが良さそうですね。

コード部分も含め全部書き写しているので、間違いやタイポがあったら感想でご指摘、修正依頼等お願いします。

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

Material UIのoutlined inputのstyleをwithStylesで変更する

Material UI のInputのデザインを編集するための情報がいまいちわかり辛かったのでここにメモする。
Material UIでもともと定義されているTextFieldのデザインを変更して汎用的なコンポーネントを作りたいときの方法。
withStyles()を使ってTextFieldに必要なスタイル情報を渡せばいい。
ただし、TextFieldpropsに初期値を設定したい場合(すべてのTextFieldvariant="outlined"を指定する場合など)には、WithStyles型を継承した自前のpropsの型を定義する必要がある。
withStylesで作った関数型コンポーネントには二つ目のカッコに入っているコンポーネントが受け取ることができる任意のpropsを渡すことができ,そのコンポーネントと同様の挙動をとる。
要するにwithStylesを使うと、style要素以外の属性がすべてもとのコンポーネントと共通のコンポーネントを作ることができる(?)。

path/to/styles.tsx
export const OutlinedInputStyle = {
    root: {
       // defaultのpaddingとmarginを無効にする
        margin: 0,
        padding: 0,
     // defaultのpaddingとmarginを無効にするとplaceholderのlabelがずれることがあるので調整する  
    '& label':{
      transform: 'translate(16px, 16px) scale(1)'
    },
    '& label.Mui-focused': {
      color: YourFavoriteColor,
     },

     '& .MuiInput-underline:after': {
      borderBottomColor: YourFavoriteColor,
     },
    '& .MuiOutlinedInput-root': {
      '& fieldset': {
        borderColor: YourFavoriteColor,
      },
      '&:hover fieldset': {
        borderColor: YourFavoriteColor,
      },
      '&.Mui-focused fieldset': {
        borderColor: YourFavoriteColor,
      },
    },
    '& .MuiOutlinedInput-input': {
        padding: 12,
    }
  },
}

冒頭でimportしているMediumやTextInputPropsは別ファイルで独自に定義している

import React from 'react';
import {Medium} from "./Fonts"
import {
    createStyles,
} from '@material-ui/core/styles';
import { TextField, InputAdornment, withStyles,WithStyles, InputProps} from '@material-ui/core';
import {
    TextInputProps,
} from '../../models';
import { OutlinedInputProps } from '@material-ui/core/OutlinedInput';
import {OutlinedInputStyle} from "./Styles"

const formStyles = createStyles(OutlinedInputStyle);

//TextFieldの一部のプロパティだけに初期値を与えたい場合、たとえばvariant="outlined"だけ設定したいときに
//WithStyles<typeof formStyles>型を継承してpropsを作る
interface CustomTextInputProps extends WithStyles<typeof formStyles>{
    inputClassName?: string,
    inputName?: string,
    onChange?:(event: React.ChangeEvent<HTMLInputElement>) => void,
    onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void,
    onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void
    value?: string
    placeholder?: string, 
    type?: string,
    fullWidth?:boolean,
    InputProps?:InputProps
}
//ひな型となるTextInputを作る. propsは自前で定義したCustomTextInputProps型
export const TextInput: React.FC<CustomTextInputProps> = (props) => {
    return(
            <TextField
                variant={"outlined"}
                {...props}
            />
    )
}
//propsに初期値を設定しない場合は、withStypes(formStyles)(TextField)と書く
//ひな型にスタイルを付与したStyledTextInputを作る
export const StyledTextInput = withStyles(formStyles)(TextInput);

path/to/somewhere.tsx
//... 省略
  return(
    <StyledTextInput
      // add props here
      // example:
      // inputProps={
      //    here are input props
      // }
      // ! TextFieldのpropsがすべて使える
    />
  )
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React公式の「state とライフサイクル」のclassをhooksを使って関数に書き換えしてみた

概要

classで書かれているcomponentDidMountとcomponentWillUnmountを useEffect を使って関数にしてみます。stateはuseStateを使って管理します。
実行される内容はタイマーで時間を動的に刻まれていくものです。

React公式
https://ja.reactjs.org/docs/state-and-lifecycle.html

公式からソースを抜粋

class Clock extends React.Component {
    constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

こちらを関数に書き換えます。

const Clock: React.FC = () => {
  let [state, setState] = React.useState({ date: new Date() });
  const dateObj = { date: new Date() };
  let timerID: any;

  useEffect(() => {
    timerID = setInterval(() => tick(), 1000);
    return () => {
      clearInterval(timerID);
    };
  });

  function tick() {
    setState({ date: new Date() });
  }

  return (
    <div className="App">
      <h1>Hello, world!</h1>
      <h2>It is {dateObj.date.toLocaleTimeString()}</h2>
    </div>
  );
};

useEffectがcomponentDidMountとcomponentWillUnmountの役割を果たしています。

詳しくは「副作用フックの利用法」読むと良いです。
https://ja.reactjs.org/docs/hooks-effect.html

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