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

React 18 + Suspense下点击按钮触发搜索的状态管理问题

如何实现仅点击按钮时触发React搜索(结合React 18 Suspense与react-query)

我是React 18和Suspense的新手,之前一直用ASP.NET MVC开发。现在想实现一个功能:点击表单按钮,把表单输入值传给带[FromQuery]特性的Web API的HttpGet方法,再把返回结果渲染到div里。

在ASP.NET MVC里我是这么写按钮点击事件的:

const btnSearch = document.getElementById('btnSearch');
btnSearch.addEventListener("click", function() {
    executeMySearch();
    return false;
});

executeMySearch方法里获取表单值、调用接口并渲染HTML:

const searchresults = document.getElementById('searchresults');
let formData = new FormData(document.forms[0]);

fetch('/Index?handler=MySearchMethod', {
        method: 'post',
        body: new URLSearchParams(formData),
    }).then(function (response) {
        return response.text();
    }).then(function (html)  {
        searchresults.innerHTML = html;

但React的实现逻辑完全不一样,上面的代码只是用来说明需求。我现在的问题是:希望只有点击搜索按钮时才执行搜索,但现在点一次按钮后,用户修改表单输入会重复触发搜索。我知道原因是isSearch状态一直是true,表单输入变化触发组件重新渲染时会再次执行搜索。

我试过把setIsSearch传给MySearchResults组件,在渲染后设为false,但会导致搜索结果闪一下就消失;也试过在选择框变更时把isSearch设为false,但搜索结果会直接消失,都不符合预期。


后端Web API代码(可正常运行)

[HttpGet("MySearchMethod")]
public async Task<List<MySearchResult>> MySearchMethod([FromQuery]MySearchForm mySearchForm)
{
    return await _myRepository.GetMySearchResults(mySearchForm);
}

现有React组件代码

搜索组件结构与状态

const MySearchComponent = () => {
    const [myEnum, setMyEnum] = useState(0);
    function onChangeMyEnum(myEnumId : number){
        setMyEnum(myEnumId);
    }
    // 其他三个下拉框的状态与 onChange 方法...
    const [isSearch, setIsSearch] = useState(false);

    return (
        <>
            <form>
                <div>
                    <ErrorBoundary FallbackComponent={MyErrorHandler}>
                        <h2>搜索条件选择</h2>
                        <Suspense fallback={<Spinner/>}>
                            <MyEnums onChange={onChangeMyEnum} />
                            {/* 其他三个懒加载的下拉选择组件 */}
                        </Suspense>
                    </ErrorBoundary>
                </div>
                <button className='btn btn-blue' onClick={(e) => {
                    e.preventDefault();
                    setIsSearch(true);
                }}>搜索</button>
            </form>
            <div>
                {
                    isSearch === true ?
                        <ErrorBoundary FallbackComponent={MyErrorHandler}>
                            <Suspense fallback={<Spinner/>}>
                                <MySearchResults myEnum={myEnum} {/* 其他搜索条件 */} />
                            </Suspense>
                        </ErrorBoundary>
                    : <span>&nbsp;</span>
                }
            </div>
        </>
    );
};

MySearchResults组件(使用react-query)

import React from 'react';
import { useQuery } from 'react-query';
import MySearchResult from './MySearchResult';

const fetchMySearchResults = async (myEnumId : number /* 其他条件参数 */) => {
    let url = `${process.env.REACT_APP_MY_API}/GetMySearchResults/?myEnumId=${myEnumId}&/* 其他查询参数 */`;
    const response = await fetch(url);
    return response.json();
}

const MySearchResults = (props : any) => {
    const {data} = useQuery(
        ['myquery', props.myEnum, /* 其他搜索条件 */],
        () => fetchMySearchResults(props.myEnum /* 其他搜索条件 */),
        {
            suspense:true
        }
    );
    
    return (
        <>
            <table>
                <thead>
                    <tr>
                        <th>列标题1</th>
                        <th>列标题2</th>
                        {/* 其他表头 */}
                    </tr>
                </thead>
                <tbody>
                    {data.map((mySearchResult: any) => (
                        {/* 渲染每条结果的逻辑 */}
                    ))}
                </tbody>
            </table>
        </>
    );
};

export default MySearchResults;

解决方案

核心思路是把搜索条件的"提交版本"和"实时输入版本"分开,只有点击搜索按钮时,才将当前的实时输入值同步到提交版本,用提交版本作为查询的依赖,这样实时输入变化不会触发查询,只有点击按钮才会更新提交版本,进而触发搜索。

步骤1:修改搜索组件的状态管理

在MySearchComponent中新增一组状态,用来存储点击搜索按钮时的查询条件快照:

const MySearchComponent = () => {
    // 实时输入的状态(用户修改下拉框时更新)
    const [myEnum, setMyEnum] = useState(0);
    // 其他实时输入状态...

    // 提交版本的查询条件(只有点击搜索时才更新)
    const [searchParams, setSearchParams] = useState({
        myEnum: 0,
        // 其他初始条件...
    });
    const [isSearch, setIsSearch] = useState(false);

    // 搜索按钮点击事件:同步实时输入到提交版本,并触发搜索
    const handleSearch = (e) => {
        e.preventDefault();
        setSearchParams({
            myEnum,
            // 同步其他实时输入状态...
        });
        setIsSearch(true);
    };

    // 其他下拉框的onChange方法不变...

    return (
        <>
            <form>
                {/* 下拉选择组件部分不变,依然绑定实时状态 */}
                <button className='btn btn-blue' onClick={handleSearch}>搜索</button>
            </form>
            <div>
                {isSearch ? (
                    <ErrorBoundary FallbackComponent={MyErrorHandler}>
                        <Suspense fallback={<Spinner/>}>
                            {/* 把提交版本的searchParams传给结果组件,而不是实时状态 */}
                            <MySearchResults {...searchParams} />
                        </Suspense>
                    </ErrorBoundary>
                ) : <span>&nbsp;</span>}
            </div>
        </>
    );
};

步骤2:调整MySearchResults组件的查询依赖

将useQuery的查询键和参数改为从searchParams(提交版本)获取,这样只有当searchParams变化时(即点击搜索按钮时),才会重新执行查询:

const MySearchResults = (props : any) => {
    const {data} = useQuery(
        // 用提交版本的参数作为查询键
        ['myquery', props.myEnum, /* 其他props中的提交参数 */],
        () => fetchMySearchResults(props.myEnum /* 其他props中的提交参数 */),
        {
            suspense:true,
            // 可选:设置staleTime,避免缓存自动失效重复请求
            staleTime: 5 * 60 * 1000 // 5分钟
        }
    );
    
    // 渲染逻辑不变...
};

可选优化:处理搜索结果的保留与清空

如果希望用户修改实时输入时,搜索结果依然保留,直到再次点击搜索,那么不需要在下拉框变更时修改isSearch。如果需要用户修改条件时清空结果,可以在每个下拉框的onChange方法中添加setIsSearch(false),但这样会清空结果,根据你的需求选择即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 08:45:30