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

6jygbczu  于 2023-11-19  发布在  React
关注(0)|答案(1)|浏览(158)

我已经创建了一个应用程序,使用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.filter而不是allCharacters.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;

xghobddn

xghobddn1#

  • 不要将Redux状态复制到本地状态,将所有新数据附加到Redux状态下的characters数组中。
  • useEffect钩子为同一页发起重复调用,在allCharacters数组中创建重复。您可以直接从存储中选择的characters状态和本地searchTerm筛选器状态值计算filteredCharacters

character.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>
  );
};


x1c 0d1x的数据

注意事项/建议

  • useEffect钩子在React.StrictMode组件的开发版本中运行得更频繁。StrictMode在沙箱中被注解掉,因此不会进行重复的API调用。
  • 不要总是将获取的数据推入characters数组,而是将数据保存在Map或Set中(* 不允许数据重复 *),并使用selectCharacters将状态转换为UI的数组。
  • 您已经在使用Redux-Toolkit,集成Redux-Toolkit Query并将Thunks转换为端点,并让RTKQ处理去重API调用和缓存结果。

相关问题