68
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Reactで「CSV一括登録機能」 を開発した話

(2017/12/13)弊社アドベントカレンダーに追加したので気持ち程度修正しました。

CSV一括登録機能?

下記のようなことをしたい!

  • ドラッグアンドドロップでCSVをアップロード
  • CSVをJSONにパース
  • JSONを配列に変換し、Storeへ格納
  • フォーム量産

今回は上2つの実装について書きます。

D&D

ReactのD&Dライブラリはこんな感じ。
react-dndが有名だけど、今回はreact-dropzoneを使った。
OS標準のFile Promptを呼び出すのが簡単だった。

FileUpload.js
import Dropzone from 'react-dropzone';
import Button from 'components/atoms/Button';

class FileUpload extends Component {

  constructor(props) {
    super(props);
    this.state = {
      isDragReject: false,
    };
    ...
  }

  onDrop() {
    this.setState({
      isDragReject: false,
    });
  }

  onDropRejected() {
    this.setState({
      isDragReject: true,
    });
  }

  handleReflect(results) {
    this.props.onComplete(results);
  }

  handleParseCsv(files) {...}

  render() {
    let dropzoneRef;
    return (
      <div className={style.fileUpload}>
        <div className={style.dropArea}>
          <Dropzone
            onDrop={this.onDrop}
            onDropAccepted={this.handleParseCsv}
            onDropRejected={this.onDropRejected}
            accept="text/csv, application/vnd.ms-excel"
            disableClick
            multiple={false}
            className={style.dropzone}
            activeClassName={style.active}
            rejectClassName={style.reject}
            ref={(node) => { dropzoneRef = node; }}
          >
            <p>Try dropping CSV file here, or click to select files to upload.</p>
            {(() => (this.state.isDragReject ?
              <p className={style.rejectMessage}>ファイルタイプが違います</p>
              : '')
            )()}
          </Dropzone>
          <Button
            onClick={() => { dropzoneRef.open(); }}
          />
        </div>
      </div>
    );
  }
}
  • stateのisDragRejectでアップロードが失敗した時のメッセージの出し分けしてる
  • 今回はCSVしか受け付けたくないので、Dropzoneコンポーネントのaccept="text/csv, application/vnd.ms-excel"のところでファイルタイプを指定してる。MacとWindowsでCSVのファイルタイプの指定の仕方が違うらしいので2つ書いてる

CSV to JSON

CSVをJSONにパースする部分はpapaparseを使った。ドキュメントが豊富で使いやすかった。
csvtojsonはよくわからんエラーが出て諦めた。issueたてたらPR来てたのでもうすぐ直るはず。

:imp::imp::imp:Shift-JIS:imp::imp::imp:

CSVは文字コードがShift-JISになってる可能性があるので、文字化けしないようにエンコードする必要ある!!!!
つらい!!!!Unicodeであって欲しい!!!
でも便利なライブラリある!!!!ありがとうございます:innocent::innocent:

encoding.js
https://github.com/polygonplanet/encoding.js/blob/master/README_ja.md

npmではencoding-japaneseという名前で配信されてる。

FileUpload.js
import Papa from 'papaparse';
import Encoding from 'encoding-japanese';

class FileUpload extends Component {

  constructor(props) {
    ...
  }

  handleReflect(results) {
    this.props.onComplete(results);
  }

  handleParseCsv(files) {
    const file = files[0];
    const reader = new FileReader();
    reader.onload = (e) => {
      const codes = new Uint8Array(e.target.result);
      const encoding = Encoding.detect(codes);
      const unicodeString = Encoding.convert(codes, {
        to: 'unicode',
        from: encoding,
        type: 'string',
      });
      Papa.parse(unicodeString, {
        header: true,
        dynamicTyping: true,
        skipEmptyLines: true,
        complete: (results) => {
          this.handleReflect(results);
        },
      });
    };
    reader.readAsArrayBuffer(file);
  }

  render() {...}
}
  • header: trueにするとヘッダーの文字列をJSONのキーにしてくれる
  • reader.onloadでファイルの読み込みが完了したときの処理を定義する
  • 読み込んだファイルをUint8Arrayオブジェクトを使ってバイト列に変換
  • encoding.jsを使って現在の文字コードを取得
  • Shift-JISをUnicodeに変換(Unicodeのときはそのまま)
  • 文字コード変換したデータをpapa parseに渡して、JSONにパースする
  • 今回はCSVの中に数字が含まれていたのでdynamicTypingを指定して、数字がstringに変換されないようにしている
  • 完了したら、親コンポーネントで定義しているデータをフォームに反映する処理(handleReflect)を実行する
  • さいごに、reader.readAsArrayBufferでファイルの読み込みを実行する

今回は、文字コードの変換とCSVのパースを組み合わせるところではまって大変でした。
Encoding.converttoutf-8を指定すると実行できなかったのでUnicodeに変えたらうまくいきました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
68
Help us understand the problem. What are the problem?