Javaエンジニア、React+Firebaseでアプリを作る

趣味で作ったものいろいろ

Draft.jsで何も出てこない→出てるはず

Facebook社のReact用リッチエディタDraft.js。 Overview | Draft.jsにあるサンプルを試したけれど、何も出てこなかったときのメモ。

ソースコード

import React from 'react';
import {Editor, EditorState} from 'draft-js';
import 'draft-js/dist/Draft.css';

function MyEditor() {
  const [editorState, setEditorState] = React.useState(
    () => EditorState.createEmpty(),
  );

  return <Editor editorState={editorState} onChange={setEditorState} />;
}

export default MyEditor

このMyEditorコンポーネントを親コンポーネントから呼び出しています。

実行結果です。

f:id:yucatio:20211120092127p:plain

何も出てきてません。いえ、実は出ています。Editorがあるあたりをクリックします。

f:id:yucatio:20211120093511p:plain

そうするとカーソルが出てきます。何か文字を入力すると文字が出てきます。これがEditorの本体です。

f:id:yucatio:20211120092517p:plain

見やすくするためにborderを入れましょう。

function MyEditor() {
  const [editorState, setEditorState] = React.useState(
    () => EditorState.createEmpty(),
  );

  return (
    <div style={{border: '1px solid #666666'}}>
      <Editor editorState={editorState} onChange={setEditorState} />
    </div>
  );
}

f:id:yucatio:20211120091912p:plain

これで開発が始められそうです。

参考: reactjs - Can't figure out why draft-js is not shown - Stack Overflow

Javaで1日の始まりと終わりを取得する

Javaで1日の始まりと終わりを取得します。

LoalDateから取得する場合

LocalTime.MINLocalTime.MAXを使います。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse("2021-11-01", formatter);

LocalDateTime startOfDay = date.atTime(LocalTime.MIN);  // 2021-11-01T00:00
LocalDateTime endOfDay = date.atTime(LocalTime.MAX);  // 2021-11-01T23:59:59.999999999

LocalDateTimeから取得する場合

LocalDateTimetoLocalDate()で一旦LocalDateに変換してから、上記と同様にLocalTime.MINLocalTime.MAXを使います。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime datetime = LocalDateTime.parse("2021-11-01 12:15:30", formatter);

LocalDateTime startOfDay = datetime.toLocalDate().atTime(LocalTime.MIN);  // 2021-11-01T00:00
LocalDateTime endOfDay = datetime.toLocalDate().atTime(LocalTime.MAX);  // 2021-11-01T23:59:59.999999999

参考にしたページ

www.baeldung.com

JavaのLocalDateTime.parse()に日付だけ渡すとエラー

JavaのLocalDateTime.parse()で日付のみを指定したらエラーになりました。回避策も書いておきます。

コード

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDateTime datetime = LocalDateTime.parse("2021-11-01", formatter);

エラー

Exception in thread "main" java.time.format.DateTimeParseException: Text '2021-11-01' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2021-11-01 of type java.time.format.Parsed
    at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2023)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1958)
    at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:494)

一旦LocalDateでパースし、LocalDateTimeに変換する必要があります。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse("2021-11-01", formatter);
LocalDateTime datetime = date.atStartOfDay();

Firestoreのtransaction中にドキュメントをaddする

Firestoreではtransaction機能が使え、複数の書き込みをまとめることができます。

transactionを使用するときには、以下のコードのようにrunTransactionを利用します。2つ目の引数にトランザクションの内容を書いた関数を渡します。

await runTransaction(db, async (transaction) => {
  // TODO ここにトランザクションの内容を書く
})

コールバックに渡ってくるtransactionにはgetsetupdatedeleteが用意されています。addがありませんね。

f:id:yucatio:20211026160219p:plain

addはdoc()とset()を組み合わせる

add()doc()set()を組み合わせることで実現します。

await runTransaction(db, async (transaction) => {
  const docRef = doc(collection(db, "my_collection"))
  transaction.set(docRef, {
    text: "addded from transaction"
  })
})

こちらを実行すると、以下のようにランダムなIDで登録されているのが分かります。

f:id:yucatio:20211026192835p:plain

環境

firebase: 9.1.3

Firestoreで文字列を正規表現で制限するセキュリティルール

Firestoreのセキュリティルールでは、文字列を正規表現で制限することができます。悪意のあるユーザにおかしな値を登録されないようアプリと同じ制限をかけておきましょう。

String.matches(regex)

String.matchesを使うと、フィールドに登録される値が 指定された正規表現を満たす場合にのみ、DBに登録することができます。 記述はこのようになります。

// 電話番号(ハイフンなし)
allow create, update: if request.resource.data.tel.matches("^0[0-9]{9,10}$")
// メールアドレスっぽい文字列
// size()も組み合わせて使いましょう
allow create, update: if request.resource.data.email.matches("^[-a-zA-Z0-9_\\.]+@[-a-zA-Z0-9_\\.]+$") &&
                         request.resource.data.email.size() <= 255

String.matches()の使用例

例として参加登録フォームを作ってみます。

f:id:yucatio:20211026132400p:plain:w350

データ構造はこのようになっています。

f:id:yucatio:20211026150453p:plain

電話番号は0とそれに続く数字が9個か10個です。メールアドレスは半角英数と、記号は-_.が使えます。@マークは1つだけです。 セキュリティルールの全体はこのようになります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /applications/{application} {
      function validApplication(docData) {
        return docData.tel.matches("^0[0-9]{9,10}$") &&
               docData.email.matches("^[-a-zA-Z0-9_\\.]+@[-a-zA-Z0-9_\\.]+$") &&
               docData.email.size() <= 255;
      }
      allow create, update: if validApplication(request.resource.data)
    }
  }
}

正規表現に合うように登録してみます。

f:id:yucatio:20211026132400p:plain:w350

Firebaseのコンソールで確認すると登録されていることが分かります。

f:id:yucatio:20211026141753p:plain

電話番号を12桁で登録してみます。

f:id:yucatio:20211026141917p:plain:w350

エラーになり、コンソールを確認すると登録されていないことが分かります。

f:id:yucatio:20211017172609p:plain

次に、メールアドレスに使えない文字列を使ってみます。

f:id:yucatio:20211026143959p:plain:w350

こちらもエラーになり、コンソールを確認すると登録されていないことが分かります。

f:id:yucatio:20211017172609p:plain

リンク

正規表現はこちらのものを参考にしました。 qiita.com

Firestoreで数値の範囲を制限するセキュリティルール

Firestoreのセキュリティルールで数値の範囲を制限することができます。悪意のあるユーザにより思わぬ値を登録されてしまい、誤動作を起こすことを防げます。

フィールドの大小比較を使う

数値の範囲を指定するには、フィールドの値を大小比較します。 例えば、"age"(年齢)フィールドを18以上65以下に制限するには以下のように記述します。

allow create, update: if request.resource.data.age >= 18 &&
                         request.resource.data.age <= 65

数値の範囲の使用例

例として参加登録フォームを作ってみます。

f:id:yucatio:20211024160316p:plain:w300

データ構造はこのようになっています。

f:id:yucatio:20211024172031p:plain

年齢を必須かつ18以上65以下という条件を、セキュリティルールで書くとこのようになります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /applications/{application} {
      function validApplication(docData) {
        return docData.age is int && 
               docData.age >= 18 &&
               docData.age <= 65;
      }
      allow create, update: if validApplication(request.resource.data)
    }
  }
}

18歳で登録してみます。

f:id:yucatio:20211024161032p:plain:w300

Firebaseのコンソールで確認すると登録されていることが分かります。

f:id:yucatio:20211024164702p:plain

-(マイナス)30歳で登録してみると、エラーになり、コンソールを確認すると登録されていないことが分かります。

f:id:yucatio:20211024163343p:plain:w300

f:id:yucatio:20211017172609p:plain

以上でFirestoreのセキュリティルールで数値の範囲を制限することができました。

Firestoreで文字列の長さを制限するセキュリティルール

Firestoreのセキュリティルールで文字列の長さを制限することができます。悪意のあるユーザによりとても長い文字列が登録されてしまい、結果過大な請求が発生するのを防ぐためにも、文字列の長さを制限しておくのはおすすめです。

string.size()

文字列のstring.size()メソッドを使用することで文字列の長さを取得できます。これと大小比較を組み合わせてルールを作成します。 例えば、"text"フィールドを16文字以下に制限するには以下のように記述します。

allow create, update: if request.resource.data.text.size() <= 16

String.size()の使用例

例としてTODOアプリを考えます。入力はタスク名とステータス(実施状況)です。

f:id:yucatio:20211017171414p:plain:w300

データ構造はこのようになっています。

f:id:yucatio:20211017230600p:plain:w350

タスク名を必須かつ16文字以下という条件を、セキュリティルールで書くとこのようになります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{todo} {
      function validTodo(docData) {
        return docData.text.size() > 0 &&
               docData.text.size() <= 16;
      }
      
      allow read: if true;

      allow create, update: if validTodo(request.resource.data);
    }
  }
}

6文字のタスク名で登録してみます。

f:id:yucatio:20211018121053p:plain:w250

Firebaseのコンソールで確認すると登録されていることが分かります。

f:id:yucatio:20211018121104p:plain

長いタスク名(18文字)で登録してみると、エラーになり、コンソールを確認すると登録されていないことが分かります。

f:id:yucatio:20211018120759p:plain

以上でFirestoreのセキュリティルールで文字列の長さを制限することができました。