【问题标题】:Testing Asynchronous Code (useEffect + fetch) In React Components在 React 组件中测试异步代码(useEffect + fetch)
【发布时间】:2021-07-04 08:53:52
【问题描述】:

我正在尝试弄清楚如何测试使用 useEffect 进行 API 调用以获取数据的更新状态的组件。我认为有几件事情在我能再说下去之前是很重要的,那就是我正在使用的文件/包。

首先,我有一个名为 App.tsx 的主要组件,在 App.tsx 内部,在 useEffect 内部,我对外部 API 进行 fetch 调用以获取 Queen 的歌曲数组。我还渲染了一个<Song /> 组件,使用.map 迭代每首歌曲,并使用.filter 根据文本输入在UI 上过滤歌曲。我正在使用自定义钩子。这是我为该组件及其自定义钩子提供的代码。

// App.tsx

type ISong = {
  id: number;
  title: string;
  lyrics: string;
  album: string;
};

export default function App() {
  const { songs, songError } = useSongs();
  const { formData, handleFilterSongs } = useForm();

  return (
    <Paper>
      <h1>Queen Songs</h1>
      <FilterSongs handleFilterSongs={handleFilterSongs} />
      <section>
        {songError ? (
          <p>Error loading songs...</p>
        ) : !songs ? (
          <>
            <p data-testid="loadingText">Loading...</p>
            <Loader />
          </>
        ) : (
          <Grid container>
            {songs
              .filter(
                (song: ISong) =>
                  song.title
                    .toLowerCase()
                    .includes(formData.filter.toLowerCase()) ||
                  song.album
                    .toLowerCase()
                    .includes(formData.filter.toLowerCase()) ||
                  song.lyrics
                    .toLowerCase()
                    .split(" ")
                    .join(" ")
                    .includes(formData.filter.toLowerCase())
              )
              .map((song: ISong) => (
                <Grid key={song.id} item>
                  <Song song={song} />
                </Grid>
              ))}
          </Grid>
        )}
      </section>
    </Paper>
  );
}

// useSongs.tsx

type ISongs = {
    id: number;
    title: string;
    lyrics: string;
    album: string;
  }[];
  
  type IError = {
    message: string;
  };

export default function useSongs() {
    const [songs, setSongs] = useState<ISongs | null>(null);
    const [songError, setSongError] = useState<IError | null>(null);

    useEffect(() => {
      fetch("https://queen-songs.herokuapp.com/songs")
        .then(res => res.json())
        .then(songs => setSongs(songs))
        .catch(err => setSongError(err));
      }, []);
    
      return {songs, songError}
}

接下来是我的 App.test.tsx 文件。我使用react-testing-libraryjest-dom/extend-expect 进行测试覆盖。这是我的测试文件代码。我一直在观看有关此事的 youtube 教程,并阅读了很多文章,但我仍然无法弄清楚。

// App.test.tsx

import * as rctl from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import App from "./App";

// @ts-ignore
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () =>
      Promise.resolve({
        value: [{title: "title1", album: "album1", lyrics: "asdf", id: 1}, {title: "title2", album: "album2", lyrics: "zxcv", id: 2}, etc...],
      }),
  })
);

describe.only("The App component should", () => {
  it("load songs from an API call after initial render", async () => {
    await rctl.act(async () => {
      await rctl.render(<App />).debug();
      rctl.screen.debug();
    });
  });
});

这段代码给了我以下错误信息


 FAIL  src/pages/App/App.test.tsx
  App
    × loads the songs on render (117 ms)

  ● App › loads the songs on render

    TypeError: Cannot read property 'then' of undefined

      17 |
      18 |     useEffect(() => {
    > 19 |       fetch("https://queen-songs.herokuapp.com/songs")
         |       ^
      20 |         .then(res => res.json())
      21 |         .then(songs => {
      22 |           setSongs(songs)

      at src/pages/App/useSongs.ts:19:7
      at invokePassiveEffectCreate (node_modules/react-dom/cjs/react-dom.development.js:23487:20)
      at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:3945:14)     
      at HTMLUnknownElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:338:25) 
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)       
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
      at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:3994:16)     
      at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:4056:31)
      at flushPassiveEffectsImpl (node_modules/react-dom/cjs/react-dom.development.js:23574:9)
      at unstable_runWithPriority (node_modules/scheduler/cjs/scheduler.development.js:468:12)
      at runWithPriority$1 (node_modules/react-dom/cjs/react-dom.development.js:11276:10)
      at flushPassiveEffects (node_modules/react-dom/cjs/react-dom.development.js:23447:14)
      at Object.<anonymous>.flushWork (node_modules/react-dom/cjs/react-dom-test-utils.development.js:992:10)
      at flushWorkAndMicroTasks (node_modules/react-dom/cjs/react-dom-test-utils.development.js:1001:5)    
      at node_modules/react-dom/cjs/react-dom-test-utils.development.js:1080:11

  console.log
    <body>
      <div>
        <div
          class="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
          style="text-align: center; overflow: hidden; min-height: 100vh;"
        >
          <h1>
            Queen Songs
          </h1>
          <div
            style="display: flex; flex-flow: column; justify-content: center; text-align: center;"
          >
            <input
              data-testid="input"
              id="filter"
              name="filter"
              placeholder="Search by title, album name, or lyrics here..."
              style="width: 18.75rem; height: 1.875rem; align-self: center; text-align: center; font-style: italic;"
              type="text"
            />
          </div>
          <section
            style="display: flex; flex-flow: row wrap; justify-content: center; align-items: center;"      
          >
            <p
              data-testid="loadingText"
            >
              Loading...
            </p>
            <div
              class="line-container"
            >
              <div
                class="line"
                data-testid="loader-line"
              />
            </div>
          </section>
        </div>
      </div>
    </body>

      at Object.debug (node_modules/@testing-library/react/dist/pure.js:107:13)

  console.log
    <body>
      <div>
        <div
          class="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
          style="text-align: center; overflow: hidden; min-height: 100vh;"
        >
          <h1>
            Queen Songs
          </h1>
          <div
            style="display: flex; flex-flow: column; justify-content: center; text-align: center;"
          >
            <input
              data-testid="input"
              id="filter"
              name="filter"
              placeholder="Search by title, album name, or lyrics here..."
              style="width: 18.75rem; height: 1.875rem; align-self: center; text-align: center; font-style: italic;"
              type="text"
            />
          </div>
          <section
            style="display: flex; flex-flow: row wrap; justify-content: center; align-items: center;"      
          >
            <p
              data-testid="loadingText"
            >
              Loading...
            </p>
            <div
              class="line-container"
            >
              <div
                class="line"
                data-testid="loader-line"
              />
            </div>
          </section>
        </div>
      </div>
    </body>

      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:82:13)

  console.error
    Error: Uncaught [TypeError: Cannot read property 'then' of undefined]
        at reportException (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\helpers\runtime-script-errors.js:62:24)
        at innerInvokeEventListeners (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:341:9)
        at invokeEventListeners (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
        at HTMLUnknownElementImpl._dispatch (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
        at HTMLUnknownElementImpl.dispatchEvent (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:94:17)
        at HTMLUnknownElement.dispatchEvent (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\generated\EventTarget.js:231:34)
        at Object.invokeGuardedCallbackDev (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:3994:16)
        at invokeGuardedCallback (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:4056:31)
        at flushPassiveEffectsImpl (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:23574:9)
        at unstable_runWithPriority (C:\Users\brian\Code\cra-queen-api-fe\node_modules\scheduler\cjs\scheduler.development.js:468:12) TypeError: Cannot read property 'then' of undefined
        at C:\Users\brian\Code\cra-queen-api-fe\src\pages\App\useSongs.ts:19:7
        at invokePassiveEffectCreate (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:23487:20)
        at HTMLUnknownElement.callCallback (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:3945:14)
        at HTMLUnknownElement.callTheUserObjectsOperation (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\generated\EventListener.js:26:30)
        at innerInvokeEventListeners (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:338:25)
        at invokeEventListeners (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
        at HTMLUnknownElementImpl._dispatch (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
        at HTMLUnknownElementImpl.dispatchEvent (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:94:17)
        at HTMLUnknownElement.dispatchEvent (C:\Users\brian\Code\cra-queen-api-fe\node_modules\jsdom\lib\jsdom\living\generated\EventTarget.js:231:34)
        at Object.invokeGuardedCallbackDev (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:3994:16)
        at invokeGuardedCallback (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:4056:31)
        at flushPassiveEffectsImpl (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:23574:9)
        at unstable_runWithPriority (C:\Users\brian\Code\cra-queen-api-fe\node_modules\scheduler\cjs\scheduler.development.js:468:12)
        at runWithPriority$1 (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:11276:10)
        at flushPassiveEffects (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom.development.js:23447:14)
        at Object.<anonymous>.flushWork (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom-test-utils.development.js:992:10)
        at flushWorkAndMicroTasks (C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom-test-utils.development.js:1001:5)
        at C:\Users\brian\Code\cra-queen-api-fe\node_modules\react-dom\cjs\react-dom-test-utils.development.js:1080:11
        at processTicksAndRejections (node:internal/process/task_queues:94:5)

      at VirtualConsole.<anonymous> (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
      at reportException (node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:28)      
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:341:9)  
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)       
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)

  console.error
    The above error occurred in the <App> component:
    
        at App (C:\Users\brian\Code\cra-queen-api-fe\src\pages\App\App.tsx:18:32)
    
    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.

      at logCapturedError (node_modules/react-dom/cjs/react-dom.development.js:20085:23)
      at update.callback (node_modules/react-dom/cjs/react-dom.development.js:20118:5)
      at callCallback (node_modules/react-dom/cjs/react-dom.development.js:12318:12)
      at commitUpdateQueue (node_modules/react-dom/cjs/react-dom.development.js:12339:9)
      at commitLifeCycles (node_modules/react-dom/cjs/react-dom.development.js:20736:11)
      at commitLayoutEffects (node_modules/react-dom/cjs/react-dom.development.js:23426:7)
      at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:3945:14)     

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.59 s, estimated 3 s
Ran all test suites related to changed files.

老实说,我在这里完全迷失了,我不知道下一步该做什么。我通常的解决问题的能力没有帮助,所以我想我会求助于 SO 寻求帮助。感谢您阅读所有这些内容以及您可能提供的任何帮助。

编辑:我剥离了 sn-ps 中大部分 CSS 的代码,使其更具可读性,这就是为什么 screen.debug() 日志包含一些 CSS 而代码不包含的原因。

编辑:我将 useEffect 方法更改为使用 async/await,现在我的测试工作正常,但我仍然有与以前相同的输出。这是更新后的useEffect 和代码输出。

// Updated useSongs.tsx

export default function useSongs() {
    const [songs, setSongs] = useState<ISongs | null>(null);
    const [songError, setSongError] = useState<IError | null>(null);

    useEffect(() => {
      (async() => {
        try {
          const fetchSongs = await fetch("https://queen-songs.herokuapp.com/songs");
          const data = await fetchSongs.json();
          setSongs(data);
        } catch (error) {
          setSongError(error);
        }
      })()
    }, []);
    
      return {songs, songError}
}

 // Updated testOutput

 PASS  src/pages/App/App.test.tsx
  App
    √ loads the songs on render (52 ms)

  console.log
    <body>
      <div>
        <div
          class="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded"
          style="text-align: center; overflow: hidden; min-height: 100vh;"
        >
          <h1>
            Queen Songs
          </h1>
          <section
            style="display: flex; flex-flow: row wrap; justify-content: center; align-items: center;"       
          >
            <p
              data-testid="loadingText"
            >
              Loading...
            </p>
            <div
              class="line-container"
            >
              <div
                class="line"
                data-testid="loader-line"
              />
            </div>
          </section>
        </div>
      </div>
    </body>

      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:82:13)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.878 s, estimated 1 s
Ran all test suites related to changed files.

我希望测试显示 HTML useEffect 运行并且状态更新后,加载文本应该消失了。

【问题讨论】:

  • 当你将 then(res => res.json()).then(...) 合并为一个 'then' 时会发生什么?似乎是如果 fetch 失败,第二个 then 会在 undefined object 上调用
  • @MichaelRovinsky 我将 useEffect 函数更新为 async/await(我在上面发布了新代码),但在状态更新后代码未显示更新后的 DOM。它只是在状态更新之前显示。
  • 添加 useEffect(() => console.log(songs), [songs]) 到你的组件,看看歌曲是否真的更新了
  • 显然没有更新。我添加了一个console.log(数据,歌曲),它注销了数据数组,但歌曲设置为空。有趣的是,该应用实际上会在进行映射和过滤的用户界面上进行更新。
  • 我不明白您为什么需要自定义挂钩。把你所有的 useState/useEffect 放到组件本身,看看有没有帮助...

标签: reactjs asynchronous testing jestjs


【解决方案1】:

通过将初始值从 null 更改为空白数组,我能够使用更新的 DOM 获得通过测试。

我还将生成的测试代码更改为以下内容。


describe.only("App", () => {
  it("loads the songs on render", async () => {
    let container: any;
    await rctl.act(async () => {
      container = rctl.render(<App />);
      await rctl.waitFor(async () => {
        await waitFor(async () => {
          expect(await container.findByTestId("grid")).toBeInTheDocument();
        });
      });
    });
  });
});

这仍然没有像真正的 DOM 那样显示所有值,但它确实显示了 data-testid="grid" 所在的 Grid 组件,这足以证明代码在更新之前已成功访问至少。我仍然希望有人能弄清楚如何使用 setSongs 使用更新的状态值测试代码(尽管在将其设置为 API 数据后状态没有更新)。

【讨论】:

    【解决方案2】:

    我也终于想出了如何在加载数据的情况下异步测试这段代码。

    秘诀是首先将data-testid="identifier" 放在您希望测试等待的节点上,然后在测试文件中运行await waitFor(() =&gt; findAllByTestId("grid"));。我的代码的最终测试看起来像这样。

    
    describe("App", () => {
      it("renders without crashing", async () => {
        const { getByText, findAllByTestId, container } = render(<App />);
        await waitFor(() => findAllByTestId("grid"));
        expect(getByText("The Night Comes Down")).toBeInTheDocument();
      });
    });
    
    

    screen.debug 在网格列表中显示&lt;Song&gt; 组件所在的所有数据,如下所示:

    
    <body>
            <div>
              <div
                class="MuiPaper-root MuiPaper-elevation5 MuiPaper-rounded"
                style="text-align: center; overflow: hidden; min-height: 100vh;"
              >
                <h1>
                  Queen Songs
                </h1>
                <div
                  style="display: flex; flex-flow: column; justify-content: center; text-align: center;"
                >
                  <input
                    data-testid="input"
                    id="filter"
                    name="filter"
                    placeholder="Search by title, album name, or lyrics here..."
                    style="width: 18.75rem; height: 1.875rem; align-self: center; text-align: center; font-style: italic;"
                    type="text"
                  />
                </div>
                <section
                  style="display: flex; flex-flow: row wrap; justify-content: center; align-items: center;"
                >
                  <div
                    class="MuiGrid-root MuiGrid-container MuiGrid-spacing-xs-3 MuiGrid-align-items-xs-baseline MuiGrid-justify-xs-center"
                    data-testid="grid"
                    style="margin: 1.25rem;"
                  >
                    <section>
                      <div
                        class="MuiGrid-root MuiGrid-item"
                        data-testid="grid"
                        style=""
                      >
                        <div
                          class="MuiPaper-root MuiCard-root makeStyles-root-1 MuiPaper-elevation24 MuiPaper-rounded"
                        >
                          <a
                            aria-disabled="false"
                            class="MuiButtonBase-root MuiCardActionArea-root"
                            href="http://localhost/songs/1"
                            tabindex="0"
                          >
                            <img
                              alt="queen album cover"
                              class="MuiCardMedia-root MuiCardMedia-media MuiCardMedia-img"
                              src="/queen3.jpg"
                            />
                            <div
                              class="MuiCardContent-root"
                            >
                              <h2
                                class="MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom"
                                style="text-decoration: underline;"
                              >
                                Bohemian Rhapsody
                              </h2>
                              <p
                                class="MuiTypography-root MuiTypography-body2 MuiTypography-colorTextSecondary"
                                data-testid="lyrics"
                              />
                            </div>
                            <span
                              class="MuiCardActionArea-focusHighlight"
                            />
                          </a>
                          <div
                            class="MuiCardActions-root MuiCardActions-spacing"
                          >
                            <button
                              class="MuiButtonBase-root MuiButton-root MuiButton-contained brand-button MuiButton-containedPrimary MuiButton-disableElevation"
                              data-testid="button"
                              tabindex="0"
                              type="button"
                            >
                              <span
                                class="MuiButton-label"
                              >
                                Show More Lyrics
                              </span>
                              <span
                                class="MuiTouchRipple-root"
                              />
                            </button>
                          </div>
                        </div>
                      </div>
                    </section>
                    <section>
                      <div
                        class="MuiGrid-root MuiGrid-item"
                        data-testid="grid"
                        style=""
                      >
                        <div
                          class="MuiPaper-root MuiCard-root makeStyles-root-1 MuiPaper-elevation24 MuiPaper-rounded"
                        >
                          <a
                            aria-disabled="false"
                            class="MuiButtonBase-root MuiCardActionArea-root"
                            href="http://localhost/songs/2"
                            tabindex="0"
                          >
                            <img
                              alt="queen album cover"
                              class="MuiCardMedia-root MuiCardMedia-media MuiCardMedia-img"
                              src="/queen.webp"
                            />
                            <div
                              class="MuiCardContent-root"
                            >
                              <h2
                                class="MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom"
                                style="text-decoration: underline;"
                              >
                                Fat Bottomed Girls
                              </h2>
                              <p
                                class="MuiTypography-root MuiTypography-body2 MuiTypography-colorTextSecondary"
                                data-testid="lyrics"
                              />
                            </div>
                            <span
                              class="MuiCardActionArea-focusHighlight"
                            />
                          </a>
                          <div
                            class="MuiCardActions-root MuiCardActions-spacing"
                          >
                            <button
                              class="MuiButtonBase-root MuiButton-root MuiButton-contained brand-button MuiButton-containedPrimary MuiButton-disableElevation"
                              data-testid="button"
                              tabindex="0"
                              type="button"
                            >
                              <s...
    
          at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:82:13)
    
    
    Test Suites: 4 passed, 4 total
    Tests:       4 passed, 4 total
    Snapshots:   0 total
    Time:        3.768 s
    Ran all test suites related to changed files.
    
    

    更新后的组件看起来像这样......

    
    type ISong = {
      id: number;
      title: string;
      lyrics: string;
      album: string;
    };
    
    export default function App() {
      const { songs, songError } = useSongs();
      const { formData, handleFilterSongs } = useForm();
    
      return (
        <Paper
          elevation={5}
          style={{ textAlign: "center", overflow: "hidden", minHeight: "100vh" }}
        >
          <h1>Queen Songs</h1>
          {songs ? <FilterSongs handleFilterSongs={handleFilterSongs} /> : null}
          <section
            style={{
              display: "flex",
              flexFlow: "row wrap",
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            {songError.message.length ? (
              <p>Error loading songs...</p>
            ) : !songs ? (
              <>
                <p data-testid="loadingText">Loading...</p>
                <Loader />
              </>
            ) : (
              <Grid
                style={{ margin: "1.25rem" }}
                container
                spacing={3}
                justify="center"
                alignItems="baseline"
                data-testid="grid"
              >
                {songs
                  .filter(
                    (song: ISong) =>
                      song.title
                        .toLowerCase()
                        .includes(formData.filter.toLowerCase()) ||
                      song.album
                        .toLowerCase()
                        .includes(formData.filter.toLowerCase()) ||
                      song.lyrics
                        .toLowerCase()
                        .split(" ")
                        .join(" ")
                        .includes(formData.filter.toLowerCase())
                  )
                  .map((song: ISong) => (
                    <section key={song.id}>
                      <Suspense fallback={<Loader />}>
                        <Grid data-testid="grid" item>
                          <Song song={song} />
                        </Grid>
                      </Suspense>
                    </section>
                  ))}
              </Grid>
            )}
          </section>
        </Paper>
      );
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-22
      • 2022-01-26
      • 2019-10-30
      • 2021-04-14
      • 2016-08-13
      • 1970-01-01
      • 2023-03-19
      • 2018-10-23
      相关资源
      最近更新 更多