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

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

ただいまの
回答率

87.37%

Reactで、JSONファイルの表示の仕方が分からない

解決済

回答 1

投稿

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

score 15

前提・実現したいこと

https://jsonplaceholder.typicode.com/

上記サイトからpostsを取得して、postsのidをkeyにしたオブジェクトを作成する。
valueには、apiから取得したkeyが対応するpostsのオブジェクトと、postsIdが一致するcommentsの配列を入れたい。
それを50投稿文作り以下のようなJSONファイルの形式で出力したい。

{
  "1": {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
    "comments": [
      {
        "postId": 1,
        "id": 1,
        "name": "id labore ex et quam laborum",
        "email": "Eliseo@gardner.biz",
        "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
      },
      {
        "postId": 1,
        "id": 2,
        "name": "quo vero reiciendis velit similique earum",
        "email": "Jayne_Kuhic@sydney.com",
        "body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et"
      },
      {
        "postId": 1,
        "id": 3,
        "name": "odio adipisci rerum aut animi",
        "email": "Nikita@garfield.biz",
        "body": "quia molestiae reprehenderit quasi aspernatur\naut expedita occaecati aliquam eveniet laudantium\nomnis quibusdam delectus saepe quia accusamus maiores nam est\ncum et ducimus et vero voluptates excepturi deleniti ratione"
      }
    ]
  },

}

そして、postsとcommentのapiを取得し、画面に表示することができました。
イメージ説明
postの表示画像

イメージ説明
commentの表示画像

しかし以下の点が分かりません。
・valueには、apiから取得したkeyが対応するpostsのオブジェクトと、postsIdが一致するcommentsの配列を入れ方
・50投稿文ループさせる方法がわからない(mapやwhileの指定が分からず)
・JSON形式にオブジェクトを入れる方法(JSONファイルを出力するにはnode.jsを使う方法しか出てこず分からない)

全てじゃなくて大丈夫なので、解決方法やヒントなどを教えていただきたいです。
よろしくお願いいたします。

発生している問題・エラーメッセージ

import React, {useState, useEffect} from 'react'
import axios from 'axios'

const ApiFetch = () => {

    const [posts, setPosts] = useState([])

    useEffect(() => {
        axios.get('https://jsonplaceholder.typicode.com/posts')
        .then(res => {
            setPosts(res.data)
        })
    }, [])

    const [comments, setComments] = useState([])

    useEffect(() => {
      axios.get('https://jsonplaceholder.typicode.com/comments')
      .then(res2 => {
          setComments(res2.data)
      })
  }, [])

    return (
        <div>
            <ul>


            {
                posts.map((post) => (
               <div> 
                <h1>{ post.id }</h1>
                <ul>
<li>"userId": {post.id}</li>
<li>"userId": {post.title}</li>
<li>"userId": {post.body}</li>
                </ul>                
                  </div>

                )
                )
            }
            {
   comments.map((comment) => (
    <div> 
     <h1>{ posts.id }</h1>
     <ul>
<li>"userId": {comment.id}</li>
<li>"title": {comment.title}</li>
<li>"body": {comment.body}</li>
     </ul>                
       </div>
   )
   )
}
            </ul>

        </div>
    )
}

export default ApiFetch

試したこと

補足情報(FW/ツールのバージョンなど)

ReactはこちらのPackage.jsonを使っています。

{
  "name": "react-file",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "axios": "^0.24.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

質問にある以下の3点

しかし以下の点が分かりません。
・valueには、apiから取得したkeyが対応するpostsのオブジェクトと、postsIdが一致するcommentsの配列を入れ方
・50投稿文ループさせる方法がわからない(mapやwhileの指定が分からず)
・JSON形式にオブジェクトを入れる方法(JSONファイルを出力するにはnode.jsを使う方法しか出てこず分からない)

のうち、はじめの2点を
(1) postsとcommentsをマージしたオブジェクトを作る
3点目を
(2)ファイルのダウンロード方法
として回答します。

(1) postsとcommentsをマージしたオブジェクトを作る

そういうときは、(まずは) lodashを使って切り抜けたいです。(※ 備考も参照ください)

const merge = (posts, comments, maxPostId) => {

  const filteredComments = comments.filter(({ postId }) => postId <= maxPostId);

  const groupByPost = _(filteredComments)
    .groupBy('postId')
    .mapValues(
      (comments, postId) => ({ 
        ...posts.find(({ id }) => id === +postId),
        comments
      })
    )
    .value();

  return groupByPost;
}


という関数を作っておいて、

const mergedPosts = merge(posts, comments, 50);
setPosts(mergedPosts);


とすればいけます。 

細かいところの注意点ですが、_.groupByでpostIdによるグループ化をした後の_.mapValues に与えている関数の第二引数 postId には、postId が文字列で渡ってきます。なので

...posts.find(({ id }) => id === +postId),


のところで、+postId で使っている単項加算+を忘れて単に

...posts.find(({ id }) => id === postId),


としてしまうと、findが失敗してpostの内容がマージされなくなります。

備考: 最近、lodashは重すぎるといった意見(例: mizchiさん)により、現場によっては、lodashの各メソッドを自分で書くこともありますが、その場合は以下が参考になります。

(2)ファイルのダウンロード方法

マージして作成したオブジェクトをJSONファイルにしてダウンロードするには、Blobを使えばよいかと思います。たとえば、前掲の動作確認のサンプル の中で、 Promise.all が成功したときの処理に以下を追加します。

    Promise.all(fetchers).then(([{ data: posts }, { data: comments }]) => {
      const mergedPosts = merge(posts, comments, 50);
      setPosts(mergedPosts);

+     // ファイルとしてダウンロード
+     const data = new Blob([JSON.stringify(mergedPosts, null, 4)], {type: 'application/json'});
+     const link = document.createElement('a');
+     link.href = window.URL.createObjectURL(data);
+     link.setAttribute('download', 'mergedPosts.json');
+     link.click();
    }); 

追記

上記の (1) で、lodash を使うコードを回答しましたが、lodashを使わない merge関数のコード例も挙げておきます。

const merge = (posts, comments, maxPostCount) => 
    posts.slice(0, maxPostCount)
         .reduce((obj, post) => { 
           obj[post.id] = { 
             ...post,
             comments: comments.filter(({ postId }) => post.id === postId)
           };
           return obj;          
         }, {});


先の(1)で挙げたコードでは、mergeの第三引数を、postIdの最大値maxPostIdにしていましたが、postの個数の上限値maxPostCount に変更しました。

追記2

上記の回答で、codepenに挙げたコード(のlodashを使うほう)をお手元で試すには、以下の手順で実行できるかと思います。

(1) 依存モジュールのインストール

npm install lodash

axios はすでにインスト−ルされているかと思いますが、まだであれば、

npm install axios

(2) src/components/ApiFetch.js を作成

以下のように作成します。

import { useEffect, useState } from 'react';
import axios from 'axios';
import _ from 'lodash';

const POSTS_URL = 'https://jsonplaceholder.typicode.com/posts';
const COMMENTS_URL = 'https://jsonplaceholder.typicode.com/comments';

const merge = (posts, comments, maxPostId) => {

  const filteredComments = comments.filter(({ postId }) => postId <= maxPostId);

  const groupByPost = _(filteredComments)
    .groupBy('postId')
    .mapValues(
      (comments, postId) => ({
        ...posts.find(({ id }) => id === +postId),
        comments
      })
    )
    .value();

  return groupByPost;
}

const ApiFetch = () => {

  const [posts, setPosts] = useState({});

  useEffect(() => {
    const urls = [POSTS_URL, COMMENTS_URL];
    const fetchers = urls.map(url => axios.get(url));
    Promise.all(fetchers).then(([{ data: posts }, { data: comments }]) => {
      const mergedPosts = merge(posts, comments, 50);
      setPosts(mergedPosts);
    });
  }, []);

  return (
    <pre id="posts">
      {JSON.stringify(posts, null, 4)}
    </pre>
  );
}

export default ApiFetch;

(3) App.jsからApiFetchをimport 

これで、App.jsで、

import ApiFetch from './components/ApiFetch';


とすることで、Appの返すJSXの中で、<ApiFetch /> をどこかに書けば、動作確認できると思われます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/11/25 21:47

    連絡ありがとうございます。
    コードありがとうございます。
    上記コードを貼り付けて試してみたんですが、

    Failed to compile.

    src/components/ApiFetch.js
    Line 10:25: '_' is not defined no-undef
    Line 29:40: 'axios' is not defined no-undef

    Search for the keywords to learn more about each error.

    のようなエラーが出ました。
    axiosをnpmでインストールしても変化がなかったんですが、
    何か不足しているという感じでしょうか。
    質問が多く申し訳ありません。
    よろしくお願いします。

    キャンセル

  • 2021/11/25 23:05

    CodePen では依存モジュールを import する行を書かなかったりして、ローカルとはちょっと勝手が違う部分があり、そのままコピペでは使えないので、お手元の環境でApiFetch.js を作成する場合について、追記2 を回答に書きました。これを試してみていただけますでしょうか?

    キャンセル

  • 2021/11/26 11:31

    回答ありがとうございます。
    全て解決できました。丁寧な解説ありがとうございました。

    キャンセル

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

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

関連した質問

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