如何在React宝可梦列表组件中实现搜索功能?附现有代码
Hey there! Let's get that search feature up and running for your Pokemon list. Your core idea—filtering Pokemon by name and updating the state to reflect results—is totally correct. Let's walk through the changes needed step by step, fixing a couple of existing state management issues along the way.
Step 1: Update Component State
First, we need to add two new state properties to support search:
searchTerm: Stores the user's input from the search boxoriginalPokemon: Keeps a copy of the full, unfiltered Pokemon data (so we can always revert to it when filtering or switching views)
Modify your state like this:
state = { url: "https://pokeapi.co/api/v2/pokemon?limit=21&offset=0", pokemon: null, originalPokemon: null, // New: Holds unfiltered raw data buttonsPage: true, searchTerm: "" // New: Tracks search input text };
Step 2: Save Original Pokemon Data
Every time you fetch Pokemon data, you need to save the raw results to originalPokemon alongside updating pokemon. This ensures we never lose the full dataset when applying filters.
Update componentDidMount:
async componentDidMount() { const res = await axios.get(this.state.url); this.setState({ pokemon: res.data["results"], originalPokemon: res.data["results"] // Save initial page data }); }
Update allData:
(Note: We're fixing the direct state modification here—always use setState instead of changing this.state directly!)
async allData() { const res = await axios.get( "https://pokeapi.co/api/v2/pokemon?limit=2000&offset=0" ); this.setState({ pokemon: res.data["results"], originalPokemon: res.data["results"], // Save full dataset buttonsPage: false }); // Remove forceUpdate()—setState automatically triggers re-renders }
Update pageView:
async pageView() { const res = await axios.get( "https://pokeapi.co/api/v2/pokemon?limit=21&offset=0" ); this.setState({ pokemon: res.data["results"], originalPokemon: res.data["results"], // Save page data buttonsPage: true }); }
Fix nextPage and previousPage:
These methods also need to update originalPokemon when fetching new pages:
async nextPage() { const res = await axios.get(this.state.url); const nextUrl = res.data.next; const res2 = await axios.get(nextUrl); this.setState({ url: nextUrl, pokemon: res2.data["results"], originalPokemon: res2.data["results"] // Save new page data }); } async previousPage() { const res = await axios.get(this.state.url); const prevUrl = res.data.previous || "https://pokeapi.co/api/v2/pokemon?limit=21&offset=0"; const res2 = await axios.get(prevUrl); this.setState({ url: prevUrl, pokemon: res2.data["results"], originalPokemon: res2.data["results"] // Save new page data }); }
Step 3: Add the Search Input
Insert a search field in your render method, right above the Pokemon list. We'll bind an onChange handler to update the search state:
// Add this inside your render() return, before the Pokemon list <div className="mt-2 ml-2"> <input type="text" placeholder="Search Pokemon by name..." className="form-control w-25" value={this.state.searchTerm} onChange={(e) => this.handleSearch(e.target.value)} /> </div>
Step 4: Implement the Search Handler
Add a handleSearch method to filter Pokemon based on the input term and update the state:
handleSearch = (term) => { const lowerCaseTerm = term.toLowerCase(); this.setState({ searchTerm: lowerCaseTerm }); let filteredPokemon; if (lowerCaseTerm === "") { // Revert to original data when search is empty filteredPokemon = [...this.state.originalPokemon]; } else { // Filter Pokemon by name (case-insensitive) filteredPokemon = this.state.originalPokemon.filter(pokemon => pokemon.name.toLowerCase().includes(lowerCaseTerm) ); } this.setState({ pokemon: filteredPokemon }); };
Key Notes & Fixes
- Never modify state directly: Earlier you used
this.state.buttonsPage = false—this breaks React's state management rules. Always usesetState()to update state, as it triggers component re-renders automatically (no need forforceUpdate()). - Preserve original data: Storing
originalPokemonensures we don't overwrite the full dataset with filtered results, letting us easily revert back when needed. - Case insensitivity: Converting both the search term and Pokemon names to lowercase ensures the search works regardless of how the user types (e.g., "pikachu", "Pikachu", or "PIKACHU" all return the same result).
Final Full Component Code
Here's the complete component with all changes applied:
import React, { Component} from "react"; import axios from "axios"; import PokemonCard from "./PokemonCard"; import loading from "../layout/images/loading.gif"; export default class PokemonList extends Component { state = { url: "https://pokeapi.co/api/v2/pokemon?limit=21&offset=0", pokemon: null, originalPokemon: null, buttonsPage: true, searchTerm: "" }; async componentDidMount() { const res = await axios.get(this.state.url); this.setState({ pokemon: res.data["results"], originalPokemon: res.data["results"] }); } async allData() { const res = await axios.get( "https://pokeapi.co/api/v2/pokemon?limit=2000&offset=0" ); this.setState({ pokemon: res.data["results"], originalPokemon: res.data["results"], buttonsPage: false }); } async pageView() { const res = await axios.get( "https://pokeapi.co/api/v2/pokemon?limit=21&offset=0" ); this.setState({ pokemon: res.data["results"], originalPokemon: res.data["results"], buttonsPage: true }); } async nextPage() { const res = await axios.get(this.state.url); const nextUrl = res.data.next; const res2 = await axios.get(nextUrl); this.setState({ url: nextUrl, pokemon: res2.data["results"], originalPokemon: res2.data["results"] }); } async previousPage() { const res = await axios.get(this.state.url); const prevUrl = res.data.previous || "https://pokeapi.co/api/v2/pokemon?limit=21&offset=0"; const res2 = await axios.get(prevUrl); this.setState({ url: prevUrl, pokemon: res2.data["results"], originalPokemon: res2.data["results"] }); } handleSearch = (term) => { const lowerCaseTerm = term.toLowerCase(); this.setState({ searchTerm: lowerCaseTerm }); let filteredPokemon; if (lowerCaseTerm === "") { filteredPokemon = [...this.state.originalPokemon]; } else { filteredPokemon = this.state.originalPokemon.filter(pokemon => pokemon.name.toLowerCase().includes(lowerCaseTerm) ); } this.setState({ pokemon: filteredPokemon }); }; render() { return ( <> {/* Search Input */} <div className="mt-2 ml-2"> <input type="text" placeholder="Search Pokemon by name..." className="form-control w-25" value={this.state.searchTerm} onChange={(e) => this.handleSearch(e.target.value)} /> </div> {/* View Toggle Buttons */} <button onClick={() => this.allData()} type="button" className="btn btn-secondary mt-2 ml-2 mb-2" style={{ display: this.state.buttonsPage ? "block" : "none" }} > All pokemons </button> <button onClick={() => this.pageView()} type="button" className="btn btn-secondary mt-2 ml-2 mb-2" style={{ display: this.state.buttonsPage ? "none" : "block" }} > Back to page view </button> {/* Pokemon List or Loading State */} {this.state.pokemon ? ( <div className="row"> {this.state.pokemon.map((pokemon) => ( <PokemonCard key={pokemon.name} pokemonName={pokemon.name} url={pokemon.url} /> ))} </div> ) : ( <img src={loading} style={{ width: "15em", height: "15em" }} className="card-img-top mx-auto mt-2" /> )} {/* Pagination Buttons */} <div className="container-fluid mx-auto" style={{ display: this.state.buttonsPage ? "block" : "none" }} > <button onClick={() => this.previousPage()} type="button" className="btn btn-secondary mt-4 ml-2 mb-2" > Previous page </button> <button onClick={() => this.nextPage()} type="button" className="btn btn-secondary mt-4 ml-2 mb-2 float-right" > Next page </button> </div> </> ); } }
Now your search feature should work smoothly: as you type in the input box, the list will automatically filter to show only Pokemon whose names include your search term. Clearing the input will bring back the full list (or current page, depending on your view mode).
内容的提问来源于stack exchange,提问作者Mladen




