Kei05
@Kei05

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Notion APIでulを作りたいが、空の配列にpushした際の挙動がわからない。

Notion APIとNext.jsでブログ構築をしているのですが、

Notion API周りの処理はnotion-blog-nextjsを元にしており、
liの親要素にulやolを作りたく、こちらの記事を参考に組んでみましたが、olの出力がうまくいかず、1から書き直した結果が後述のソースコードになります。

本題

組み直したところ空の配列にpushしている時点で挙動があやしく、原因がわかりません。

最後に添付のNotionブロックを処理するとき、
各ブロックを処理して配列に入れているのでulList.push(block);をすると1回目のループで最初のリストのオブジェクトが配列に入る認識でしたが、実際にはすべてのbulleted_list_itemが入ってしまいました。

なぜすべて入ってしまうのでしょうか。どのように記述すれば治せるのでしょうか。

長くなり申し訳ありませんが教えていただけますと幸いです。

実際のソースコード

notionBlocks.jsx
import Head from 'next/head';
import Link from 'next/link';
import { Fragment } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import styles from '@style/components/post/notionBlocks.module.scss';
import Image from 'next/image';

export const Text = ({ text }) => {
  if (!text) {
    return null;
  }

  return text.map((value, index) => {
    const {
      annotations: { bold, code, color, italic, strikethrough, underline },
      text,
    } = value;

    // テキストの装飾に関連するクラスを必要に応じて生成
    const annotationClassArray = [
      bold && styles.bold,
      code && styles.inlineCode,
      italic && styles.italic,
      strikethrough && styles.strikethrough,
      underline && styles.underline,
    ];
    const annotationClassString = annotationClassArray.filter((val) => val !== false).join(' ');

    // テキストの色の装飾に関連するオブジェクトを生成
    const styleObject = color.includes('default')
      ? {}
      : color.endsWith('_background')
      ? { textDecorationColor: `var(--u-background-color-${color.replace('_background', '')})` }
      : { color: `var(--u-text-color-${color})` };

    if (code) {
      if ('textDecorationColor' in styleObject) {
        return (
          <code key={index} className={annotationClassString}>
            <mark style={styleObject}>{text.content}</mark>
          </code>
        );
      } else {
        return (
          <code key={index} className={annotationClassString} style={styleObject}>
            {text.content}
          </code>
        );
      }
    } else if (annotationClassString !== '' || !!Object.keys(styleObject).length) {
      if ('textDecorationColor' in styleObject) {
        return (
          <mark key={index} className={annotationClassString} style={styleObject}>
            {text.content}
          </mark>
        );
      } else {
        return (
          <span key={index} className={annotationClassString} style={styleObject}>
            {text.content}
          </span>
        );
      }
    } else {
      return <Fragment key={index}>{text.content}</Fragment>;
    }
  });
};

export const createListWrap = (listBlocksArr) => {
  const listWrapClass = styles.listWrapper;
  switch (listBlocksArr[0].type) {
    case 'bulleted_list_item':
      return (
        <ul key={listBlocksArr[0].id} className={`${listWrapClass} ${listWrapClass}_ul`}>
          {listBlocksArr.map((listBlock) => renderBlock(listBlock))}
        </ul>
      );
    case 'numbered_list_item':
      return (
        <ol key={listBlocksArr[0].id} className={`${listWrapClass} ${listWrapClass}_ol`}>
          {listBlocksArr.map((listBlock) => renderBlock(listBlock))}
        </ol>
      );
  }
};

const renderBlock = (block) => {
  const { type, id } = block;
  const value = block[type];

  switch (type) {
    case 'paragraph':
      if (value.rich_text.length === 0) {
        return <br key={id} />;
      } else {
        return (
          <p key={id}>
            <Text text={value.rich_text} />
          </p>
        );
      }
    case 'heading_1':
      return (
        <h1 className={styles.heading_1}>
          <Text text={value.rich_text} />
        </h1>
      );
    case 'heading_2':
      return (
        <h2 className={styles.heading_2}>
          <Text text={value.rich_text} />
        </h2>
      );
    case 'heading_3':
      return (
        <h3 className={styles.heading_3}>
          <Text text={value.rich_text} />
        </h3>
      );
    case 'bulleted_list_item':
    case 'numbered_list_item':
      return (
        <li key={id} className={styles.listItem}>
          <Text text={value.rich_text} />
        </li>
      );
    case 'to_do':
      return (
        <div>
          <label htmlFor={id}>
            <input type="checkbox" id={id} defaultChecked={value.checked} />{' '}
            <Text text={value.rich_text} />
          </label>
        </div>
      );
    case 'toggle':
      return (
        <details>
          <summary>
            <Text text={value.rich_text} />
          </summary>
          {value.children?.map((block) => (
            <Fragment key={block.id}>{renderBlock(block)}</Fragment>
          ))}
        </details>
      );
    case 'child_page':
      return <p>{value.title}</p>;
    case 'image':
      const src = value.type === 'external' ? value.external.url : value.file.url;
      const caption = value.caption ? value.caption[0]?.plain_text : '';
      return (
        <figure className={styles.image}>
          <img src={src} alt={caption} loading="lazy" decoding="async" />
          {/* <Image src={src} alt={caption} loading="lazy" decoding="async" /> */}
          {caption && <figcaption className={styles.imageCaption}>{caption}</figcaption>}
        </figure>
      );
    case 'divider':
      return <hr key={id} />;
    case 'quote':
      return (
        <blockquote key={id} className={styles.quote}>
          {value.rich_text[0].plain_text}
        </blockquote>
      );
    case 'code':
      return (
        <SyntaxHighlighter
          key={id}
          language={value.language}
          style={tomorrow}
          customStyle={{ fontFamily: 'var(--font-mono)' }}
          showLineNumbers
          className={styles.codeBlock}
        >
          {value.rich_text[0].plain_text}
        </SyntaxHighlighter>
      );
    case 'callout':
      let emojiClass, noteTitle;
      switch (value.icon.emoji) {
        case '🚨':
          emojiClass = styles.noteAlert;
          break;
        case '⚠️':
          emojiClass = styles.noteWarn;
          noteTitle = '注意';
          break;
        case '':
          emojiClass = styles.noteCheck;
          break;
        default:
          emojiClass = styles.noteInfo;
          break;
      }
      return (
        <div key={id} className={styles.note}>
          <p className={`${styles.noteTitle} ${emojiClass}`} aria-label={noteTitle}>
            {noteTitle}
          </p>
          <p className={styles.noteContents}>
            <Text text={value.rich_text} />
          </p>
        </div>
      );
    case 'file':
      const src_file = value.type === 'external' ? value.external.url : value.file.url;
      const splitSourceArray = src_file.split('/');
      const lastElementInArray = splitSourceArray[splitSourceArray.length - 1];
      const caption_file = value.caption ? value.caption[0]?.plain_text : '';
      return (
        <figure>
          <div>
            📎{' '}
            <Link href={src_file} passHref>
              {lastElementInArray.split('?')[0]}
            </Link>
          </div>
          {caption_file && <figcaption>{caption_file}</figcaption>}
        </figure>
      );
    case 'bookmark':
      const href = value.url;
      return (
        <a href={href} target="_brank">
          {href}
        </a>
      );
    default:
      return `❌ Unsupported block (${
        type === 'unsupported' ? 'unsupported by Notion API' : type
      })`;
  }
};

export function NotionBlocks({ page, blocks }) {
  if (!page || !blocks) return <></>;

  let ulList = [];
  let olList = [];
  let outPutList = '';

  const TYPE_UL = 'bulleted_list_item';
  const TYPE_OL = 'numbered_list_item';

  return (
    <>
      <Head></Head>

      {/* Body */}
      <section className={styles.module}>
        {/* {blocks.map((block) => renderBlock(block))} */}
        {console.log('=====▼開始▼=====')}

        {blocks.map((block) => {
          console.log(block);

          if (block.type === TYPE_UL) {
            console.log(block);
            ulList.push(block);
            console.log(ulList);
            console.log('ulです');
          } else {
            console.log('それ以外です');
          }
        })}
        {console.log('=====▲終了▲=====')}
      </section>
    </>
  );
}

Notionのブロック

スクリーンショット 2022-11-03 0.59.47.png

追記1

export function NotionBlocks({ page, blocks }) {
  if (!page || !blocks) return <></>;

  let ulList = [];
  let olList = [];
  let outPutList = '';

  const TYPE_UL = 'bulleted_list_item';
  const TYPE_OL = 'numbered_list_item';
  console.log(ulList);
  return (
    <>
      <Head></Head>

      {/* Body */}
      <section className={styles.module}>
        {/* {blocks.map((block) => renderBlock(block))} */}
        {console.log(ulList)}
        {console.log('=====▼開始▼=====')}

        {blocks.map((block) => {
          console.log(block);

          if (block.type === TYPE_UL) {
            ulList.push(block);
          } else {
          }
        })}
        {console.log('=====▲終了▲=====')}
      </section>
    </>
  );
}

スクリーンショット 2022-11-03 23.37.40.png

0

1Answer

1回目のループで最初のリストのオブジェクトが配列に入る認識

その認識で合っています。配列の中身を確認する処理かブロックとして出力する処理に間違いがあって誤解しているのではないかと思いますが、コードが省略されていて分かりません。省略せずに全部書いてください。

0Like

Comments

  1. @Kei05

    Questioner

    失礼いたしました。
    質問文に記載のソースコードをファイル内全体のものに修正しましたので再度ご確認お願いします!
  2. ありがとうございます。

    > 各ブロックを処理して配列に入れているのでulList.push(block);をすると1回目のループで最初のリストのオブジェクトが配列に入る認識でしたが、実際にはすべてのbulleted_list_itemが入ってしまいました。

    これは何を見て判断しましたか?今のコードだと、 bulleted_list_item ブロックを処理するごとに

    console.log(block);
    ulList.push(block);
    console.log(ulList);
    console.log('ulです');

    が実行されて、順に ulList に追加されていく様子がログに出そうですが。
  3. @Kei05

    Questioner

    > 順に ulList に追加されていく様子がログに出そうですが。
    → 自分もこの認識でしたがコンソールにオブジェクトが複数入っており、各オブジェクトの中に入っているブロックのタイプがbulleted_list_itemになっていました。

    検証するコードを少し変えたところ、ulListを宣言した直後でも配列のなかに3つのオブジェクトが入っていることが確認できました。
    こちらに関してmapの外の変数宣言時点でもオブジェクトが3つ格納されてしまっていたので、検証するコードを少し変えて追記しましたので再度ご確認いただけますでしょうか。
    コンソールに装飾を加えていますがconsole.logでも出力結果は変わらないのでそのまま見ていただけますと幸いです。
    何度もすみません
    https://qiita.com/Kei05/questions/aa91db95051ac2c87b19#%E8%BF%BD%E8%A8%981
  4. > ulListを宣言した直後でも配列のなかに3つのオブジェクトが入っていることが確認できました。

    スクリーンショットの一番上の段落ですね。ややこしいんですが、値の横の三角をクリックして展開される中身は console.log() で記録した時点の状態ではなく、クリックした時点の状態を反映します。コードを最後まで実行すると3個オブジェクトが入るので、それが表示されたということですね。記録した時点では展開前に [] と表示されているとおり空配列です。
  5. もし console.log(ulList) した時点で3個オブジェクトが入っていれば
    ▶ (3) [{…}, {…}, {…}]
    と表示されるはずです。
  6. @Kei05

    Questioner

    > 値の横の三角をクリックして展開される中身は console.log() で記録した時点の状態ではなく、クリックした時点の状態を反映します。
    こちら知らずずっと展開後の状態を見てしまっていました。

    その上で再度確認したところ実際に細かく分岐を入れた記述に誤りがあり、配列に値があったときの処理の条件式に、値が1のときを弾いてしまっていることがわかり修正したところ正しく動作しました。

    何度も細かくご回答いただきありがとうございました。
    本件解決しましたのでクローズとさせていただきます。

Your answer might help someone💌