提问人:Imaginario27 提问时间:10/27/2023 最后编辑:Imaginario27 更新时间:10/31/2023 访问量:62
Rick and Morty React + TypeScript + Redux App - 为什么过滤不起作用?
Rick and Morty React + TypeScript + Redux App - Why filtering is not working?
问:
我创建了一个应用程序,它使用 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;
答:
0赞
Drew Reese
10/27/2023
#1
- 不要将 Redux 状态复制到本地状态,将所有新数据附加到 Redux 状态的数组中。
characters
- 钩子发起对同一页面的重复调用,在数组中创建重复项。您可以直接从存储中的选定状态和本地筛选器状态值进行计算。
useEffect
allCharacters
filteredCharacters
characters
searchTerm
字符.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>
);
};
注意事项/建议
- 钩子在组件内的开发版本中更频繁地运行。 在沙盒中被注释掉,因此不会进行重复的 API 调用。
useEffect
React.StrictMode
StrictMode
- 不要总是将提取到数组中的任何内容推送到数组中,而是将数据保留在 Map 或 Set 中(不允许数据重复),并使用 将状态转换为 UI 的数组。
characters
selectCharacters
- 您已经在使用 Redux-Toolkit,集成 Redux-Toolkit Query 并将 Thunks 转换为端点,并让 RTKQ 处理重复的 API 调用和缓存结果。
评论