【问题标题】:using React 18 and suspense, do search from form and populate div with search results on button click使用 React 18 和 suspense,从表单中搜索并在单击按钮时用搜索结果填充 div
【发布时间】:2022-11-12 05:43:38
【问题描述】:

我是 React 18 和 Suspense 的新手。我以前几乎所有的 Web 开发都是在 asp.net mvc 中完成的。我想单击表单上的按钮,将表单输入值传递给具有 [FromQuery] 属性的 Web api HttpGet 方法,然后将返回呈现到 div 中。

如果我在 asp.net mvc 中这样做,我会像在 javascript 中那样连接一个按钮单击事件:

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

在 executeMySearch() 方法中,我会获取表单输入值,将它们发送到服务器,从服务器获取一些 html 并将其放入 div 中,如下所示:

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 中方法是完全不同的,我展示上面的代码只是为了演示我想要发生的事情。我希望仅在单击搜索按钮时执行搜索。我的问题是,我无法弄清楚如何管理 React 状态来实现这一点。目前,单击一次搜索按钮后,每次用户更改表单输入的值时,我的搜索都会执行。我理解为什么会发生这种情况,但我不知道如何构建我的组件,以便仅在单击搜索按钮时执行搜索。

在服务器端,我的 web api 接收一个表单并返回一个通用列表,就像这样。这工作正常:

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

在我的 React 应用程序中,我有一个搜索组件。该组件呈现一个包含以下元素的表单:

  • 四个选择,其中包含搜索条件。这些选择被包装在 React 组件中。
  • 一个搜索按钮
  • 呈现搜索结果的组件

每个选择输入都是一个 React 组件,其中包含从 Web api 获取的枚举列表。每个选择都在搜索组件中定义,如下所示:

const MyEnums = lazy(() => import('./MyEnums'));

当定义搜索组件时,这些 React 组件中的每一个都与 React 状态相关联,如下所示:

const MySearchComponent = () => {
    const [myEnum, setMyEnum] = useState(0);
    function onChangeMyEnum(myEnumId : number){
        setMyEnum(myEnumId);
    }...

我将我的搜索按钮绑定到 React 状态,如下所示:

const [isSearch, setIsSearch] = useState(false);

我的搜索组件返回一个带有搜索条件和搜索按钮的表单,以及一个包含搜索结果的 div:

    return (
            <>
            <form>
                    <div>
                        <ErrorBoundary FallbackComponent={MyErrorHandler}>
                            <h2>My Search Criteria Select</h2>
                            <Suspense fallback={<Spinner/>}>
                                <MyEnums onChange={onChangeMyEnum} />
                            </Suspense>
                        </ErrorBoundary>
                    </div>
<button className='btn btn-blue' onClick={(e) => {
                e.preventDefault();
                setIsSearch(true);
            }
                }>Search</button>
        </form>
<div>
            {
            isSearch === true ?
                <ErrorBoundary FallbackComponent={MyErrorHandler}>
                    <Suspense fallback={<Spinner/>}>
                        <MySearchResults myEnum={myEnum} [..other search criteria] />
                    </Suspense>
                </ErrorBoundary>
            : <span>&nbsp;</span>
            }
        </div>

一切正常。问题是,在第一次单击搜索按钮(执行“setIsSearch(true)”)后,每次用户更改其中一个表单输入中的选择时,搜索都会执行。我明白为什么。我的“isSearch”变量保持为真,因此当表单输入更改更改状态并且重新呈现组件时,搜索再次发生。

我尝试将“setIsSearch”方法传递给 MySearchResults 组件,并在组件呈现后调用 setIsSearch(false),但这当然完全符合它的预期。 React 状态发生变化,组件重新渲染,它看到 "isSearch" 为 false,它使搜索结果消失。当我点击我的搜索按钮时,我看到搜索结果短暂闪烁然后消失,这正是应该发生的事情。

每次选择更改时,我也尝试调用 setIsSearch(false) ,但这当然会导致我的搜索结果消失,这是不希望的。

我错过了什么?我如何构造它以便仅在单击“搜索”按钮时才进行搜索?

附言Web API 调用是在呈现时在 MySearchResults 组件内部进行的。 MySearchResults 组件如下所示:

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

const fetchMySearchResults = async (myEnumId : number [...other criteria]) => {
    let url = `${process.env.REACT_APP_MY_API}/GetMySearchResults/?myEnumId=${myEnumId}&[...other criterial]`;
    const response = await fetch(url);
    return response.json();
}

const MySearchResults = (props : any) => {
    const {data} = useQuery(['myquery', props.myEnum,...other search criteria...]
        ,() => fetchMySearchResults(props.myEnun [...other search criteria]),
        {
            suspense:true
        });
    
    return (
        <>
        <table>
            <thead>
                <tr>
                    <th>My Column Header</th>
                    <th>...and so on</th>
                </tr>
            </thead>
            <tbody>
                {data.map((mySearchResult: {
                    ...and so on
            </tbody>
            </table>
        </>
    );
};

export default MySearchResults;

【问题讨论】:

  • 搜索功能 (executeMySearch()) 实际执行在哪里?
  • @ZaeemKhaliq 我添加了呈现搜索结果的组件。呈现组件时发生搜索。
  • 你不需要在MySearchResults 中这样做。只需在搜索按钮的单击事件处理程序上执行此操作。由于您使用的是react-query,因此必须有一些标志或某种方式来手动触发获取。在您的请求完成后,如果返回了一些 data,您可以简单地呈现 MySearchResults。
  • @ZaeemKhaliq 当您说“必须有一些标志或某种方式来手动触发获取”时。我不知道该怎么做。就是那个问题。你是怎样做的?

标签: reactjs react-suspense


【解决方案1】:

useQuery 连接从MySearchResults 移动到MySearchComponent。在 useQuery 函数中使用 enabled 标志,该标志可防止查询自动运行:-

const {data} = useQuery(['myquery', props.myEnum,...other search criteria...]
    ,() => fetchMySearchResults(props.myEnun [...other search criteria]),
    {
        enabled: isSearch,
        suspense:true
    }
);

isSearch 设置为true(单击搜索按钮时执行此操作)时,查询将执行。当你得到data 时,渲染你的MySearchResults 组件,提供data 作为道具。

之后,如果您想再次触发搜索功能,您可以在搜索按钮的事件处理程序中使用useQuery 钩子提供的refetch 函数,如下所示: -

const {data, refetch} = useQuery();
const onSearchClick = (e) => {
  e.preventDefault();
  if(isSearch === true){
    refetch();  
  }else{
    setIsSearch(true);
  }
}

如需完整参考,请访问此处:-

https://react-query-v3.tanstack.com/reference/useQuery

【讨论】:

  • 谢谢!我不知道“启用”属性。我认为这将解决我的问题。我将首先尝试将 isSearch 传递到 MySearchResults 组件中,我将再次发布。谢谢,“启用”属性是我缺少的信息。
  • 恐怕这仍然行不通。我确实获得了一些新见解,但我意识到我的根本问题是:我希望在单击搜索按钮时访问搜索条件的更改状态,但我不希望搜索条件的更改状态导致我的搜索结果重新呈现。我需要以某种方式将搜索条件的状态与按钮单击的状态分开。
  • @TomRegan 很可能是因为在您的 useQuery 挂钩中,您将搜索条件(props.myEnum)作为查询键。每当查询键更改(您的搜索条件更改)时,都会重新获取它(它写在库的文档中)。您可以做的是将搜索条件存储在单独的状态中,并将不同的状态作为查询键管理,例如const [queryKey, setQueryKey] = useState(null)。当您单击搜索按钮时,在事件处理程序中组合搜索条件并将其设置为 queryKey。现在只有queryKey 更改并且只有在重新获取之后才会发生。
【解决方案2】:

使用 2 使用状态:

const [myEnum, setMyEnum] = useState(0);
const [searchEnum, setSearchEnum] = useState();
setIsSearch(true);

    function onChangeMyEnum(myEnumId : number){
        setMyEnum(myEnumId);
    }

<Suspense fallback={<Spinner/>}>
     <MyEnums onChange={onChangeMyEnum} />
</Suspense>

<button className='btn btn-blue' onClick={(e) => {
                e.preventDefault();
                setSearchEnum(myEnum)
                setIsSearch(true);

            }
                }>Search</button>

{
            isSearch === true ?
                <ErrorBoundary FallbackComponent={MyErrorHandler}>
                    <Suspense fallback={<Spinner/>}>
                        <MySearchResults myEnum={searchEnum} [..other search criteria] />
                    </Suspense>
                </ErrorBoundary>
            : <span>&nbsp;</span>
            }

如果您每次有人键入新值时只使用 myEnum ,则会触发渲染,从而触发您的搜索。 如果您通过设置在单击按钮时触发搜索的值来分隔输入和搜索,您的用户可以输入输入,但只有单击才会触发搜索。因为只有点击也会改变搜索正在监听的值。

【讨论】:

  • 感谢@Letincel 的建议,但这就是我已经在做的事情。当状态改变时组件会重新渲染。您的陈述“因为只有点击会改变搜索正在侦听的值”是不准确的。更改选择会更改组件的状态,从而导致重新渲染。乙
【解决方案3】:

这是答案的一部分——如果您不希望组件重新渲染,请不要使用 useState() 设置变量。使用 useRef() 代替。

因此,每个选择组件的更改处理程序现在定义如下:

const myEnum = useRef(0);
function onChangeMyEnum(myEnumId : number){
    myEnum.current = carrierGroupId;
}

这是我之前(错误地)调用 useState 时的样子:

const MySearchComponent = () => {
    const [myEnum, setMyEnum] = useState(0);
    function onChangeMyEnum(myEnumId : number){
        setMyEnum(myEnumId);
    }...

我将这些变量传递给我的组件,就像我以前做的一样:

<div>
    <ErrorBoundary FallbackComponent={MyErrorHandler}>
        <h2>My Search Criteria Select</h2>
        <Suspense fallback={<Spinner/>}>
            <MyEnums onChange={onChangeMyEnum} />
        </Suspense>
    </ErrorBoundary>
</div>

这可以防止选择中的每次更改导致组件重新渲染的问题。

我现在有一个不同的问题——“MySearchResults”组件在第一次单击按钮后不会重新呈现。按钮单击事件调用“setIsSearch(true);”。这会导致搜索结果只呈现一次。我需要弄清楚如何更改按钮单击中的状态,以使每次单击都重新呈现组件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-10
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 2010-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多