質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.92%

Formikで画像ファイルを入力した時にstateがリフレッシュされてしまう

受付中

回答 0

投稿

  • 評価
  • クリップ 0
  • VIEW 111

Sean2014

score 26

現在、React(TypeScript)でFormikのFormでユーザーインプットを処理するコンポーネントを作ろうとしています。Formの入力値のタイプはTextとFileです。

現時点で抱えている問題としては、特定の条件下で画像をアップしようとした時にFormの入力フィールドが(意図していないのに)リセットされてしまうという事です。

例えば、先に画像ファイルを選択してその後でテキストの欄を記入して送信ボタンを押せば全てのデータが正常にaxios.postで送信されてバックエンド側でも適切に処理されてデータが保存されるのですが、ユーザーインプットの順番を逆にして、先にテキストタイプの欄を入力してから画像を選択するとその時にフォームのテキストの入力欄がリセットされてしまうのです。さらには、ファイルの所の値も自身のローカルPCから選んだ画像ではなく"foo.txt"としてリセットされてしまいます。

下記が実際のコードです。

import React from 'react';
import Word from '../interfaces/Word.interface';
import WordsDataService from '../api/WordsDataService';
import { Formik, Form, Field, ErrorMessage } from 'formik'; 
import IWordProps from '../interfaces/IWordProps.interface';
import IWordState from '../interfaces/IWordState.interface';

class CreateWord extends React.Component<IWordProps, IWordState>{

  constructor(props: IWordProps){
    super(props)

    this.state = {
      wordId: this.props.match.params.id,
      wordData: {
        id: 0,
        ownLangWordName: '',
        targetLangWordName: '',
        ownLangExSentence: '',
        targetLangExSentence: '',
        createdDate: new Date,
        image: new File(["foo"], "foo.txt")
      }
    }

    this.onSubmit = this.onSubmit.bind(this)
    this.onChange = this.onChange.bind(this)
    this.awaitSetState = this.awaitSetState.bind(this)
  }

  componentDidMount(){
    // directly mutating the state. to be refined later 
    this.state.wordData.id = null; 
  }

  async awaitSetState(stateUpdate: Word){
    await this.setState({wordData : stateUpdate})
  }

  onChange(e: { currentTarget: HTMLInputElement; }){
    const chosenFile = e.currentTarget.files[0];
    console.log("The value of chosenFile:");
    console.log(chosenFile);

    let tempWordData = this.state.wordData;
    tempWordData.image = chosenFile;  

    this.awaitSetState(tempWordData);
    console.log("The value of this.state.wordData: ");
    console.log(this.state.wordData);
  }  

  onSubmit(values: Word){
    let word = values;

    WordsDataService.createWord(word)
    .then(() => this.props.history.push('/'))       
  }

  render(){
    let { id, ownLangWordName, 
      targetLangWordName, ownLangExSentence, 
      targetLangExSentence, createdDate, 
      image} 
      = this.state.wordData; 

    return(
      <div>
        <h2>Create Word</h2>
        <div>
          <Formik
            initialValues={{ id, ownLangWordName, 
              targetLangWordName, ownLangExSentence, 
              targetLangExSentence, createdDate, 
              image}}
            onSubmit={this.onSubmit}
            enableReinitialize={true}
          >
            {
              (props) => (
                <Form>
                  <fieldset>
                    <label>Word (Target Language)</label>&nbsp;
                    <Field type="text" name="targetLangWordName"/>
                  </fieldset>
                  <fieldset>
                    <label>Word (Own Language)</label>&nbsp;
                    <Field type="text" name="ownLangWordName"/>
                  </fieldset>
                  <fieldset>
                    <label>Sentence (Target Language)</label>&nbsp;
                    <Field type="text" size="75" name="targetLangExSentence"/>
                  </fieldset>
                  <fieldset>
                    <label>Sentence (Own Language)</label>&nbsp;
                    <Field type="text" size="75" name="ownLangExSentence"/>
                  </fieldset>
                  <fieldset>
                    <label>Date</label>&nbsp;
                    <Field type="text" name="createdDate"/>
                  </fieldset>
                  <fieldset>
                    <label>Image</label>&nbsp;
                    <input id="image" type="file" name="image" onChange={this.onChange}/>
                  </fieldset>
                  <button type="submit">Save</button>
                </Form>
              )
            }      
          </Formik>
        </div>
      </div>
    )
  }
}

export default CreateWord; 

空の文字列や"foo.txt"state.wordDataにデフォルト値として設定したものであるという所までは把握していますが、よくわからないのが「なぜファイルインプットを先に入力してテキストを後から入力したら何も問題が起こらないのに後でファイルを選択すると入力欄がリセットされてしまうのか?」という事です。

JavaScriptやTypeScriptにおいては非同期的な性質があるという事を意識する必要があるのはわかっていますが、下記のようなasync & awaitのメソッドも定義して使っているのにうまくいっていないのが現状です。

async awaitSetState(stateUpdate: Word){
    await this.setState({wordData : stateUpdate})
}

console.logで各フェーズでの変数の値を見てもみましたが、

this.awaitSetState(tempWordData);
    console.log("The value of this.state.wordData: ");
    console.log(this.state.wordData);
...

の段階ではもうFormの各入力欄が空の文字列や"foo.txt"としてリセットされてしまっていました。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る