LoginSignup
75
71

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-10-07

(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に変えたらうまくいきました。

75
71
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75
71