かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

React 文字列を改行付きにして出力したい

APIからエラーの時、改行コード付きの文字列が返ってくるので、改行を <br> に変換して出力したかったのメモ

例えばこんな文字列を改行させて表示させたい

const msg = "星宮いちご\n霧矢あおい\n紫吹蘭";

タグが入っていても文字列は文字列として出力される

改行コードを <br> に変換するのは /\n/g/\r\n|\n/g とかで replace すれば OK APIから返ってくるようなデータなら /\n/g で良さそうな気がする

str.replace で変換したものを children として出力しようとしても文字列なので文字列として出力される

// const msg = "星宮いちご\n霧矢あおい\n紫吹蘭";
const component = ({ msg }) => {
  const text = msg.replace(/\n/g, '<br />');
  return <div>{text}</div>;
}

👉 星宮いちご<br />霧矢あおい<br />紫吹蘭 という文字列が出力される

<div dangerouslySetInnerHTML={{__html: text}} /> で HTML タグを出力できるけど、あまり使いたくない方法なので別の方法にしたい。

<br /> タグを JSX で認識できる形にしなければならない

JSX は配列が扱えるので、改行コードで分割して直接 JSX な要素を作ってしまえばOK

// const msg = "星宮いちご\n霧矢あおい\n紫吹蘭";
const component = ({ msg }) => {
  const texts = msg.split("\n").map((item, index) => {
    // <></> は key を設定できないので、<React.Fragment /> を使う
    return (
      <React.Fragment key={index}>{item}<br /></React.Fragment>
    );
  });
  return <div>{texts}</div>;
}

👇

<div>
  星宮いちご<br />
  霧矢あおい<br />
  紫吹蘭<br />
</div>

<br /> タグが入り改行はできているが、最後の要素に不要な改行タグが付いてしまう。

/n (改行コード) だけを <br /> に変換する

str.split はキャプチャが使えるようなのでこれを利用する

RegExp で分割して結果に区切り文字列の一部を含める
separator がキャプチャの括弧 () を含む正規表現である場合、一致した結果が配列に含まれます。
cf. String.prototype.split() - JavaScript | MDN

つまり

const msg = "星宮いちご\n霧矢あおい\n紫吹蘭";
msg.split(/(\n)/);
// => ["星宮いちご", "↵", "霧矢あおい", "↵", "紫吹蘭"]

改行コードも配列の1要素になるので、この配列をループで回して改行コードなら <br /> タグに変換すれば良い。

// const msg = "星宮いちご\n霧矢あおい\n紫吹蘭";
const component = ({ msg }) => {
  const texts = msg.split(/(\n)/).map((item, index) => {
    return (
      <React.Fragment key={index}>
        { item.match(/\n/) ? <br /> : item }
      </React.Fragment>
    );
  });
  return <div>{texts}</div>;
}

👇

<div>
  星宮いちご
  <br />
  霧矢あおい
  <br />
  紫吹蘭
</div>

改行タグは React.createElement('br') でも作れる。
 
全部に Fragment が入ってチョット壮大すぎる気もするけど
₍ ᐢ. ̫ .ᐢ ₎👌 ヨシ!!

SAMPLE

See the Pen JSX: display <br /> by strings by KIKIKI (@kikiki_kiki) on CodePen.


JavaScript axios エラー時にもレスポンスを使いたい。

axios で API に接続している時 status 400 などでレスポンスが返ってくると catch 節で取得できるのですが、その際にも API から返される値を使いたい時のメモ

e.g. status 400 が返される時

API (Express)

router.post('/api', (req, res) => {
  // … 処理
  return res.status(400).send('エラーだよ');
});

フロント

try {
  const res = await axios.post(API_PATH, data);
} catch (err) {
  console.log(err, err.message);
}

APIエラーだよ を送っているが、status 400 で返ってきた時の err は次のような感じになる。

> POST http://localhost:3000/api/ 400 (Bad Request)
Error: Request failed with status code 400
    at createError (createError.js:16)
    at settle (settle.js:17)
    at XMLHttpRequest.handleLoad (xhr.js:61)
"Request failed with status code 400"

APIが送っている「エラーだよ」のメッセージを使いたい…

error.response を使う

エラー時に catch に入ってくる エラーオブジェクトは .response プロパティを持っていて、その中の response.data には API が send した内容が入っている。

フロント

try {
  const res = await axios.post(API_PATH, data);
} catch (err) {
  const errorMessage = err.response.data || err.message;
  console.log(errorMessage); // => "エラーだよ"
}

これで、エラーで cartch 節に処理が流れても API が送るデータを扱えるようになりました!✌️₍ ᐢ. ̫ .ᐢ ₎✌️
この方法なら status 200 で返すオブジェクト内に エラー判別をするプロパティを作らなくて済むのでとても見通しがいい感じです


JavaScript axios Content-Type の設定にはハマる

axios で express のAPI にリクエストを投げていて何故かうまく値が取れなくてハマってしまったのでメモ。

Express のAPI

const express = require('express');
const app = express();
const Joi = require('@hapi/joi');

const validation = (data) => {
  const schema = Joi.object({
    email: Joi.string().required().email(),
    password: Joi.string().min(6).required(),
  });
  return schema.validate(data, { abortEarly: false });
};

app.post('/api/login', (req, res) => {
  const { error } = validation(data);
  if( error ) {
    res.json({
      message: 'welcome to the API'
    });
  } else {
    const errorMessages = error.details.map((data) => data.message);
    res.status(400).send(errorMessages.join("\n"));
  }
});

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(3000, () => console.log('Server started !'));

問題のあったコード

import axios from 'axios';

axios.defaults.headers.common['content-type'] = 'application/json';

const getPosts = await (data) => {
  try {
    const res = await axios.post(API_SIGNUP, data);
    return res;
  } catch (err) {
    throw err;
  }
};

API を叩くので常に content-type: application/json にしたかったので default.headers で設定したのですが email is required, password is required とPOSTしているはずのデータがバリエーションで弾かれるエラーになって返ってくるばかりでした。

POST したデータは Express では req.body に入ってくるので、これを console.log してみたところ空オブジェクト{} になっていました。(POST した筈のデータがが消えている… なーぜー?)

GET / POST メソッドの headers オプションで content-type を設定すると res.body でデータが取れ問題なく動作する

import axios from 'axios';

const getPosts = await (data) => {
  try {
    const res = await axios.post(API_SIGNUP, data, {
      headers: {
        content-type: 'application/json',
      }
    });
    return res;
  } catch (err) {
    throw err;
  }
};

Request Header に違いがあった

GET / POST の headers オプションで content-type を指定すれば問題なく動作していたことから、 API 側ではなくフロントの送り方に問題があるのではと推察しました。

送信されるリクエストを見比べていたところ Request Headersの content type に違いが出ていました。

axios.defaults.headers.common['content-type'] = 'application/json'; で送信したリクエス

[content-type]: application/json, application/json;charset=utf-8

axios.post(url, data, { headers: { 'content-type': 'application/json' } }) で送信したリクエスト 

[content-type]: application/json

defaults.headers オプションで送信した Request Header の Content-Type は axios が自動的に application/json;charset=utf-8 を付けているようで application/json が二重になっています。🤔 🤔 🤔

Defaults.headers で Content-Type を設定する時は Content-Type でなければならない

defaults.headers で設定していた Content-Type のキーが content-type と小文字になっていたことが問題でした。

🙅

axios.defaults.headers.common['content-type'] = 'application/json';
  • [Request Header]: application/json, application/json;charset=utf-8
  • req.body => {}

🙆

axios.defaults.headers.common['Content-Type'] = 'application/json';
  • [Request Header]: application/json
  • req.body => { foo: bar, … }

🙅

axios.defaults.headers.common['Content-type'] = 'application/json';
  • [Request Header]: application/json, application/json;charset=utf-8
  • req.body => {}

🙅

axios.defaults.headers.common['content-Type'] = 'application/json';
  • [Request Header]: application/json, application/json;charset=utf-8
  • req.body => {}

default.headers オプションで Content-Type を設定する時は C と T が大文字の Content-Type でなければ application/json;charset=utf-8 が追加で付けられてしまうようです。

GET / POST のオプションの方は大文字小文字の区別なく受け入れられる

🙆

axios.post(url, data, {headers: {'content-type': 'application/json'}});
  • [Request Header]: application/json;char
  • req.body => { foo: bar, … }

🙆

axios.post(url, data, {headers: {'Content-Type': 'application/json'}});
  • [Request Header]: application/json;char
  • req.body => { foo: bar, … }

🙆

axios.post(url, data, {headers: {'Content-type': 'application/json'}});
  • [Request Header]: application/json;char
  • req.body => { foo: bar, … }

🙆

axios.post(url, data, {headers: {'content-Type': 'application/json'}});
  • [Request Header]: application/json;char
  • req.body => { foo: bar, … }

恐らく toLowerCase() されているのだと思います。 headers: { 'cOntEnt-type': 'application/json' } みたいな設定にしても問題ありませんでした。

 
もしかしたら Express の express.json()type オプションを上手く変えれば問題なかったのかもしれませんが、axios の default.header オプションでの Content-Type の設定は "Content-Type" という固定の文字列でしか設定できない仕様になっているのが原因だったようです。

あーハマった!


基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

  • 作者:mio
  • 発売日: 2018/05/29
  • メディア: 単行本(ソフトカバー)

Axios: Celebrating the Hymns of Greek Orthodox America

Axios: Celebrating the Hymns of Greek Orthodox America

  • 発売日: 2011/12/22
  • メディア: MP3 ダウンロード
AXIOS ってギリシャ語で正教会の用語でもあるんですね〜 (こうやってムダ知識ばかりつく)