Rick and Morty React + TypeScript + Redux App - 为什么过滤不起作用?

Rick and Morty React + TypeScript + Redux App - Why filtering is not working?

提问人:Imaginario27 提问时间:10/27/2023 最后编辑:Imaginario27 更新时间:10/31/2023 访问量:62

问:

我创建了一个应用程序,它使用 Rick and Morty API 来显示字符列表。此外,此应用程序应该能够实时过滤结果网格。此应用程序还包括该功能。但是,我在这里遇到了几个问题。InfiniteFiltering

让我给你一些背景。

这是文件:charactersSlice

import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
import { RootState } from '../../app/store';
import axios from 'axios';

export interface Character {
    id: number;
    name: string;
    species: string;
    status: string;
    origin: {
      name: string;
    };
    image: string;
}
  
interface CharactersState {
    characters: Character[];
    loading: boolean;
    error: string | null;
}
  
const initialState: CharactersState = {
    characters: [],
    loading: false,
    error: null,
};

export const fetchCharacters = createAsyncThunk<Character[], number>(
  'characters/fetchCharacters',
  async (page) => {
    const response = await axios.get(`https://rickandmortyapi.com/api/character/?page=${page}`);
    return response.data.results;
  }
);

const charactersSlice = createSlice({
    name: 'characters',
    initialState,
    reducers: {
      setCharacters: (state, action: PayloadAction<Character[]>) => {
        state.characters = action.payload;
      },
      setLoading: (state, action: PayloadAction<boolean>) => {
        state.loading = action.payload;
      },
      setError: (state, action: PayloadAction<string | null>) => {
        state.error = action.payload;
      },
    },
    extraReducers: (builder) => {
        builder
          .addCase(fetchCharacters.fulfilled, (state, action) => {
            state.characters = action.payload;
            state.loading = false;
            state.error = null;
          })
          .addCase(fetchCharacters.pending, (state) => {
            state.loading = true;
          })
          .addCase(fetchCharacters.rejected, (state, action) => {
            state.error = action.error.message || 'An error occurred.';
            state.loading = false;
          });
      },
  });

export const { setCharacters, setLoading, setError } = charactersSlice.actions;

export const selectCharacters = (state: RootState) => state.characters.characters;
export const selectLoading = (state: RootState) => state.characters.loading;
export const selectError = (state: RootState) => state.characters.error;

export default charactersSlice.reducer;

这是 charactersGrid 文件:

import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchCharacters, selectCharacters } from './charactersSlice';
import { AppDispatch } from '../../app/store';
import CharacterCard from './Character';
import CharacterSearch from './CharactersSearch';
import { Character } from './charactersSlice';
import InfiniteScroll from 'react-infinite-scroll-component';

const CharacterGrid: React.FC = () => {
  const characters = useSelector(selectCharacters);
  const dispatch: AppDispatch = useDispatch();

  const [searchTerm, setSearchTerm] = useState<string>('');
  const [page, setPage] = useState(1);
  const [allCharacters, setAllCharacters] = useState<Character[]>([]);

  useEffect(() => {
    dispatch(fetchCharacters(page));
  }, [dispatch, page]);

  useEffect(() => {
    // Append newly loaded characters to the existing array
    setAllCharacters((prevCharacters) => [...prevCharacters, ...characters]);
  }, [characters, page]);

  const filteredCharacters = allCharacters.filter((character) =>
    character.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  const loadMore = () => {
    if (searchTerm === '') {
      setPage(page + 1);
    }
  };

  return (
    <div className="character-grid-container">
      <CharacterSearch searchTerm={searchTerm} onSearchChange={setSearchTerm} />
      <div className="character-grid-title-container">
        <hr />
        <h1 className="character-grid-title">Lista de personajes</h1>
        <hr />
      </div>
      <InfiniteScroll
        dataLength={filteredCharacters.length}
        next={loadMore}
        hasMore={searchTerm === ''} // Only allow loading more if searchTerm is empty
        loader={
          searchTerm === '' ? ( // Check if searchTerm is empty
            <h4 className="notification">Loading...</h4>
          ) : null // If searchTerm is not empty, don't render anything
        }
      >
        <div className="character-grid">
          {filteredCharacters.map((character) => (
            <CharacterCard key={character.id} character={character} />
          ))}
        </div>
      </InfiniteScroll>
    </div>
  );
  
};

export default CharacterGrid;

现在发生了什么:当我向下滚动时,无限滚动工作正常。但是,如果我尝试在搜索字段中写一些东西,生活过滤不起作用。此外,如果我再次清除搜索字段,它会再次加载新的更多字符(这不应该发生)。

如果用户滚动,它应该只向数组添加新字符。此外,如果搜索字段有某个值,则不应加载更多字符,如果它再次为空,则不应加载更多字符。allCharacters

目标是过滤数组中可用的当前字符。allCharacters

显然,过滤器仅在我使用而不是在此代码中使用时才有效:characters.filterallCharacters.filter

const filteredCharacters = allCharacters.filter((character) =>
    character.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

拜托,我需要帮助来修复它。

我尝试了不同的方法,但没有奏效。

只有当应用滚动且搜索字段为空时,应用才应该能够加载更多字符。

更新答案:

非常感谢您的建议。

在发布前面的代码后,我用一些代码更新了 charactersGrid,以避免键重复并生成随机 id。

你能帮我把以前的解决方案应用于这个更新的代码吗?

// Import necessary dependencies and components from external files
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchCharacters, selectCharacters, selectError } from './charactersSlice';
import { AppDispatch } from '../../app/store';
import CharacterCard from './Character';
import CharacterSearch from './CharactersSearch';
import { Character } from './charactersSlice';
import InfiniteScroll from 'react-infinite-scroll-component';

// Define a functional React component called CharacterGrid
const CharacterGrid: React.FC = () => {
    // Retrieve characters and error from the Redux store
    const characters = useSelector(selectCharacters);
    const error = useSelector(selectError);
    const dispatch: AppDispatch = useDispatch();

    // Define and initialize state variables for search, pagination, and character data
    const [searchTerm, setSearchTerm] = useState<string>('');
    const [page, setPage] = useState(1);
    const [allCharacters, setAllCharacters] = useState<Character[]>([]);

    // Use the useEffect hook to fetch characters when the page or store changes
    useEffect(() => {
        dispatch(fetchCharacters(page));
    }, [dispatch, page]);

    // Use another useEffect to append newly loaded characters to the existing character data
    useEffect(() => {
        // Create a set of character IDs to check for duplicates
        const existingCharacterIds = allCharacters.map((character) => character.id);
        const newCharacters = characters.filter(
        (character) => !existingCharacterIds.includes(character.id)
        );

        // Append newly loaded characters to the existing array
        setAllCharacters((prevCharacters) => [...prevCharacters, ...newCharacters]);
    }, [characters, allCharacters, page]);

    // Filter characters based on the search term, and assign random keys to each character
    const filteredCharacters = allCharacters.filter((character) =>
        character.name.toLowerCase().includes(searchTerm.toLowerCase())
    );

    // Add a random key to the character
    const filteredCharactersWithRandomKeys = filteredCharacters.map((character) => {
        const randomKey = Math.random().toString(36).substring(7);
        return { ...character, randomKey };
    });

    // Function to load more characters when the InfiniteScroll component triggers it
    const loadMore = () => {
        if (searchTerm === '') {
        setPage(page + 1);
        }
    };

    // Render the component's JSX structure
    return (
        <div id="character-grid-container" className="character-grid-container">
        {/* Display the character search input component */}
        <CharacterSearch searchTerm={searchTerm} onSearchChange={setSearchTerm} />
        <div className="character-grid-title-container">
            <hr />
            <h1 className="character-grid-title">Lista de personajes</h1>
            <hr />
        </div>
        {/* Implement the InfiniteScroll component for lazy loading */}
        <InfiniteScroll
            dataLength={filteredCharactersWithRandomKeys.length}
            next={loadMore}
            hasMore={searchTerm === ''} // Only allow loading more if searchTerm is empty
            loader={
            searchTerm === '' ? ( // Check if searchTerm is empty
                error ? ( // If there's an error, display the error message
                <h4 className="notification error">{error}</h4>
                ) : (
                <h4 className="notification">Loading...</h4>
                )
            ) : null // If searchTerm is not empty, don't render anything
            }
        >
            {filteredCharactersWithRandomKeys.length === 0 ? (
            <h4 className="notification">No se ha encontrado ningún personaje...</h4>
            ) : (
            <div className="character-grid">
                {/* Map and render the CharacterCard component for each character */}
                {filteredCharactersWithRandomKeys.map((character) => (
                <CharacterCard key={character.randomKey} character={character} />
                ))}
            </div>
            )}
        </InfiniteScroll>
        </div>
    );
};

// Export the CharacterGrid component as the default export
export default CharacterGrid;

javascript reactjs redux 过滤 无限滚动

评论


答:

0赞 Drew Reese 10/27/2023 #1
  • 不要将 Redux 状态复制到本地状态,将所有新数据附加到 Redux 状态的数组中。characters
  • 钩子发起对同一页面的重复调用,在数组中创建重复项。您可以直接从存储中的选定状态和本地筛选器状态值进行计算。useEffectallCharactersfilteredCharacterscharacterssearchTerm

字符.slice

const charactersSlice = createSlice({
  name: "characters",
  initialState,
  reducers: {
    ...
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchCharacters.fulfilled, (state, action) => {
        state.characters.push(...action.payload); // <-- append characters here
        state.loading = false;
        state.error = null;
      })
      .addCase(fetchCharacters.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchCharacters.rejected, (state, action) => {
        state.error = action.error.message || "An error occurred.";
        state.loading = false;
      });
  }
});

字符网格

const CharacterGrid = () => {
  const characters = useSelector(selectCharacters);
  const dispatch: AppDispatch = useDispatch();

  const [searchTerm, setSearchTerm] = useState<string>("");
  const [page, setPage] = useState(1);

  useEffect(() => {
    dispatch(fetchCharacters(page));
  }, [dispatch, page]);

  const filteredCharacters = characters.filter((character) =>
    character.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  const loadMore = () => {
    if (searchTerm === "") {
      setPage(page + 1);
    }
  };

  return (
    <div className="character-grid-container">
      <CharacterSearch searchTerm={searchTerm} onSearchChange={setSearchTerm} />
      <div className="character-grid-title-container">
        <hr />
        <h1 className="character-grid-title">Lista de personajes</h1>
        <hr />
      </div>
      <InfiniteScroll
        dataLength={filteredCharacters.length}
        next={loadMore}
        hasMore={searchTerm === ""} // Only allow loading more if searchTerm is empty
        loader={
          searchTerm === "" ? ( // Check if searchTerm is empty
            <h4 className="notification">Loading...</h4>
          ) : null // If searchTerm is not empty, don't render anything
        }
      >
        <div className="character-grid">
          {filteredCharacters.map((character) => (
            <CharacterCard key={character.id} character={character} />
          ))}
        </div>
      </InfiniteScroll>
    </div>
  );
};

编辑 rick-and-morty-react-typescript-redux-app-why-filtering-is-not-working

注意事项/建议

  • 钩子在组件内的开发版本中更频繁地运行。 在沙盒中被注释掉,因此不会进行重复的 API 调用。useEffectReact.StrictModeStrictMode
  • 不要总是将提取到数组中的任何内容推送到数组中,而是将数据保留在 Map 或 Set 中(不允许数据重复),并使用 将状态转换为 UI 的数组。charactersselectCharacters
  • 您已经在使用 Redux-Toolkit,集成 Redux-Toolkit Query 并将 Thunks 转换为端点,并让 RTKQ 处理重复的 API 调用和缓存结果。