You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js中基于属性值过滤Rick and Morty API角色的问题排查及通用过滤组件实现咨询

Hey there! Let's break down the issues in your code first, then walk through building a reusable filter component that works for species, status, and gender.

First: Fixing Your Current Filtering Logic

Your code has a few key issues that are preventing the filtered data from showing up:

1. Event handlers can't return JSX directly

Your handleSpeciesChange and handleStatusChange functions are trying to return components like <Human /> when the select changes—but event callbacks don't work that way. The return value gets ignored entirely. Instead, you need to update component state to trigger a re-render with the filtered data.

2. Your Human component has data handling bugs

  • The Rick and Morty API returns an object with a results array (not a single character), so characterFilters.name will never exist. You need to access the results array and map over it.
  • You're hardcoding species=human in the fetch call, but you already have match.params.species—use that to make the component dynamic!
  • No loading state: You'll get errors if you try to render data before the fetch completes.

Here's a fixed version of that component (renamed to be reusable for any species):

export const SpeciesFilteredCharacters = ({ match }) => {
  const { species } = match.params;
  const [characters, setCharacters] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(`https://rickandmortyapi.com/api/character?species=${species}`)
      .then((res) => res.json())
      .then((data) => {
        setCharacters(data.results);
        setLoading(false);
      })
      .catch((err) => {
        console.error(err);
        setLoading(false);
      });
  }, [species]); // Re-run when species param changes

  if (loading) return <div>Loading characters...</div>;

  return (
    <div className="grid grid-cols-5 gap-4">
      {characters.map((character) => (
        <div key={character.id}>
          <h2>{character.name}</h2>
          <img alt={character.name} src={character.image} />
        </div>
      ))}
    </div>
  );
};

3. Fixing the Characters component's filtering

Instead of returning components in event handlers, use a single state object to track all filter values, then re-fetch data whenever those values change. Here's the revised core logic:

export const Characters = () => {
  const [characters, setCharacters] = useState([]);
  const [loading, setLoading] = useState(true);
  // Track all filter values in one state object
  const [filters, setFilters] = useState({
    species: "all",
    status: "all",
    gender: "all"
  });

  // Fetch data whenever filters change
  useEffect(() => {
    setLoading(true);
    // Build query parameters dynamically
    const queryParams = new URLSearchParams();
    Object.entries(filters).forEach(([key, value]) => {
      if (value !== "all") queryParams.append(key, value);
    });

    fetch(`https://rickandmortyapi.com/api/character?${queryParams}`)
      .then((res) => res.json())
      .then((data) => {
        setCharacters(data.results);
        setLoading(false);
      })
      .catch((err) => {
        console.error(err);
        setLoading(false);
      });
  }, [filters]);

  // Handle all filter changes with one function
  const handleFilterChange = (e) => {
    const { name, value } = e.target;
    setFilters(prev => ({ ...prev, [name]: value }));
  };

  if (loading) return <Loading />;

  return (
    <div className="p-4 font-mono text-green-500">
      <div className="flex flex-row">
        {/* Update selects to bind to filter state */}
        <div className="m-4">
          <label>Species</label>
          <select
            name="species"
            value={filters.species}
            onChange={handleFilterChange}
          >
            <option value="all">all</option>
            <option value="human">human</option>
            <option value="alien">alien</option>
            <option value="unknown">unknown</option>
          </select>
        </div>
        {/* Repeat for status and gender selects, using the same handleFilterChange */}
        <div className="m-4">
          <label>Status</label>
          <select
            name="status"
            value={filters.status}
            onChange={handleFilterChange}
          >
            <option value="all">all</option>
            <option value="alive">alive</option>
            <option value="dead">dead</option>
            <option value="unknown">unknown</option>
          </select>
        </div>
        <div className="m-4">
          <label>Gender</label>
          <select
            name="gender"
            value={filters.gender}
            onChange={handleFilterChange}
          >
            <option value="all">all</option>
            <option value="female">female</option>
            <option value="male">male</option>
            <option value="genderless">genderless</option>
            <option value="unknown">unknown</option>
          </select>
        </div>
      </div>

      <h1 className="text-4xl">Characters</h1>
      <Pagination data={characters} pageLimit={5} />
      <div className="grid grid-cols-5 gap-4">
        {characters.map((character) => (
          <div key={character.id}>
            <Character character={character} />
          </div>
        ))}
      </div>
    </div>
  );
};

Second: Building a Reusable Filter Component

Absolutely! A reusable filter component will cut down on duplicate code and make it easy to add/remove filters later. Here's how to build one:

Step 1: Create the generic filter component

This component takes a config object to render all filter controls dynamically:

export const FilterControls = ({ filters, onFilterChange }) => {
  // Define your filter configs here (easy to update later!)
  const filterConfigs = [
    {
      label: "Species",
      name: "species",
      options: [
        { value: "all", label: "all" },
        { value: "human", label: "human" },
        { value: "alien", label: "alien" },
        { value: "unknown", label: "unknown" }
      ]
    },
    {
      label: "Status",
      name: "status",
      options: [
        { value: "all", label: "all" },
        { value: "alive", label: "alive" },
        { value: "dead", label: "dead" },
        { value: "unknown", label: "unknown" }
      ]
    },
    {
      label: "Gender",
      name: "gender",
      options: [
        { value: "all", label: "all" },
        { value: "female", label: "female" },
        { value: "male", label: "male" },
        { value: "genderless", label: "genderless" },
        { value: "unknown", label: "unknown" }
      ]
    }
  ];

  const handleChange = (e) => {
    const { name, value } = e.target;
    onFilterChange(prev => ({ ...prev, [name]: value }));
  };

  return (
    <div className="flex flex-row">
      {filterConfigs.map((config) => (
        <div key={config.name} className="m-4">
          <label>{config.label}</label>
          <select
            name={config.name}
            value={filters[config.name]}
            onChange={handleChange}
          >
            {config.options.map((option) => (
              <option key={option.value} value={option.value}>
                {option.label}
              </option>
            ))}
          </select>
        </div>
      ))}
    </div>
  );
};

Step 2: Use the component in Characters

Now you can replace all the individual select boxes with this one component:

export const Characters = () => {
  const [characters, setCharacters] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filters, setFilters] = useState({
    species: "all",
    status: "all",
    gender: "all"
  });

  // Same fetch logic as before...

  if (loading) return <Loading />;

  return (
    <div className="p-4 font-mono text-green-500">
      {/* Use the reusable filter component */}
      <FilterControls filters={filters} onFilterChange={setFilters} />

      <h1 className="text-4xl">Characters</h1>
      <Pagination data={characters} pageLimit={5} />
      <div className="grid grid-cols-5 gap-4">
        {characters.map((character) => (
          <div key={character.id}>
            <Character character={character} />
          </div>
        ))}
      </div>
    </div>
  );
};

This approach keeps your code clean, maintainable, and easy to extend—if you ever want to add a new filter (like origin), just update the filterConfigs array!

内容的提问来源于stack exchange,提问作者Alice378

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 08:07:30