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

React跨组件传递API搜索结果:SearchBar与SearchResultsSection通信问题

解决无共同父组件的React组件间数据传递问题

问题根源

你遇到的onSearchResults is not a function错误,本质是两个组件没有共同父组件,无法通过props传递回调函数。SearchBar组件定义了onSearchResults作为props,但因为没有上层组件能把这个函数传递给它,所以它拿到的onSearchResults是undefined,自然无法调用。

下面是几种可行的解决方法,按推荐程度排序:


解决方案一:React Context API(React内置方案,推荐)

Context API是React官方提供的跨组件共享状态的方案,适合这种无共同父组件的场景。

步骤1:创建搜索上下文

新建一个SearchContext.tsx文件:

import React, { createContext, useContext, useState, ReactNode } from 'react';

// 定义搜索结果类型,和现有代码对齐
interface SearchResult {
  title: string;
  overview: string;
}

// 定义上下文的类型
interface SearchContextType {
  searchResults: SearchResult[];
  setSearchResults: (results: SearchResult[]) => void;
}

// 创建上下文,默认值用于类型提示
const SearchContext = createContext<SearchContextType | undefined>(undefined);

// 自定义Hook,简化上下文的使用
export const useSearchContext = () => {
  const context = useContext(SearchContext);
  if (!context) {
    throw new Error('useSearchContext must be used within a SearchProvider');
  }
  return context;
};

// 上下文Provider组件,包裹需要共享状态的组件
export const SearchProvider = ({ children }: { children: ReactNode }) => {
  const [searchResults, setSearchResults] = useState<SearchResult[]>([]);

  return (
    <SearchContext.Provider value={{ searchResults, setSearchResults }}>
      {children}
    </SearchContext.Provider>
  );
};

步骤2:用Provider包裹应用根组件

在根组件(比如App.tsx)中,用SearchProvider包裹所有需要访问搜索状态的组件:

import { SearchProvider } from './SearchContext';
import SearchBar from './SearchBar';
import SearchResultsSection from './SearchResultsSection';

function App() {
  return (
    <SearchProvider>
      {/* 这里放你的其他组件,包括SearchBar和SearchResultsSection */}
      <SearchBar currentPage="homepage" />
      <SearchResultsSection />
    </SearchProvider>
  );
}

export default App;

步骤3:修改SearchBar组件

去掉onSearchResults props,改用useSearchContext获取setSearchResults:

import React, { useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import './SearchBar.css'
import { useSearchContext } from './SearchContext'; // 导入自定义Hook

interface SearchBarProps {
  currentPage: string; // 移除onSearchResults
}

const SearchBar: React.FC<SearchBarProps> = ({ currentPage }) => {
  const [query, setQuery] = useState<string>('')
  const { setSearchResults } = useSearchContext(); // 获取更新结果的方法

  const handleSearch = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()

    const apiEndpoint =
      currentPage === 'homepage'
        ? `http://localhost:5050/homepage/search/${query}`
        : currentPage === 'movies'
        ? `http://localhost:5050/movies/search/${query}`
        : currentPage === 'series'
        ? `http://localhost:5050/series/search/${query}`
        : null

    if (!apiEndpoint) {
      console.error('Invalid current page')
      return
    }

    try {
      const response = await fetch(apiEndpoint, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      })

      if (response.ok) {
        const data = await response.json()
        setSearchResults(data); // 直接调用上下文的方法更新结果
      } else {
        console.error('Error fetching data')
      }
    } catch (error) {
      console.error('Error:', error)
    }
  }

  // 其余JSX代码保持不变
  return (
    <div>
      <form onSubmit={handleSearch} className="searchBox">
        <div className="input-group input-group-lg">
          <input
            name="search"
            type="search"
            className="form-control searchInput"
            id="search"
            placeholder={`Search ${
              currentPage === 'homepage' ? 'films, series' : currentPage
            }`}
            aria-label="Search"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
          />
          <button
            type="submit"
            className="form-control searchButton"
            id="submit"
          >
            <FontAwesomeIcon icon={faSearch} />
          </button>
        </div>
      </form>
    </div>
  )
}

export default SearchBar

步骤4:修改SearchResultsSection组件

去掉results props,改用useSearchContext获取搜索结果:

import React from 'react'
import SearchContainer from '../Navbar/SearchContainer'
import { useSearchContext } from './SearchContext'; // 导入自定义Hook

const SearchResultsSection: React.FC = () => { // 移除props
  const { searchResults } = useSearchContext(); // 获取搜索结果

  return (
    <div className="search-results-section">
      <h2>Search Results</h2>
      {searchResults.length > 0 ? (
        <ul>
          {searchResults.map((result, index) => (
            <li key={index}>
              <p>Title: {result.title}</p>
              <p>Overview: {result.overview}</p>
            </li>
          ))}
        </ul>
      ) : (
        <p>No results found.</p>
      )}

      <SearchContainer />
    </div>
  )
}

export default SearchResultsSection

解决方案二:状态管理库(如Redux Toolkit)

如果你的应用比较复杂,有很多跨组件状态需要管理,推荐用Redux Toolkit:

  1. 安装依赖:npm install @reduxjs/toolkit react-redux
  2. 创建searchSlice.ts定义状态和更新方法:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface SearchResult {
  title: string;
  overview: string;
}

interface SearchState {
  results: SearchResult[];
}

const initialState: SearchState = {
  results: [],
};

const searchSlice = createSlice({
  name: 'search',
  initialState,
  reducers: {
    setSearchResults: (state, action: PayloadAction<SearchResult[]>) => {
      state.results = action.payload;
    },
  },
});

export const { setSearchResults } = searchSlice.actions;
export default searchSlice.reducer;
  1. 配置Redux store,在根组件用Provider包裹应用。
  2. 在SearchBar中用useDispatch调用setSearchResults,在SearchResultsSection中用useSelector获取结果。

解决方案三:自定义事件(临时方案,不推荐)

如果只是临时测试,可以用浏览器自定义事件,但要注意清理监听避免内存泄漏:

修改SearchBar组件

在搜索完成后派发自定义事件:

// handleSearch函数中,获取data后
window.dispatchEvent(new CustomEvent('searchResults', { detail: data }));

修改SearchResultsSection组件

在组件挂载时监听事件,卸载时移除:

import React, { useState, useEffect } from 'react'
import SearchContainer from '../Navbar/SearchContainer'

interface SearchResult {
  title: string;
  overview: string;
}

const SearchResultsSection: React.FC = () => {
  const [results, setResults] = useState<SearchResult[]>([]);

  useEffect(() => {
    const handleSearchResults = (e: CustomEvent) => {
      setResults(e.detail);
    };
    window.addEventListener('searchResults', handleSearchResults as EventListener);
    
    // 清理监听
    return () => {
      window.removeEventListener('searchResults', handleSearchResults as EventListener);
    };
  }, []);

  // 其余JSX代码保持不变
  return (
    <div className="search-results-section">
      <h2>Search Results</h2>
      {results.length > 0 ? (
        <ul>
          {results.map((result, index) => (
            <li key={index}>
              <p>Title: {result.title}</p>
              <p>Overview: {result.overview}</p>
            </li>
          ))}
        </ul>
      ) : (
        <p>No results found.</p>
      )}

      <SearchContainer />
    </div>
  )
}

export default SearchResultsSection

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:04:56