如何仅在按钮单击时触发具有多个依赖项的 useEffect() 而没有其他任何内容 [英] How to trigger useEffect() with multiple dependencies only on button click and on nothing else

查看:11
本文介绍了如何仅在按钮单击时触发具有多个依赖项的 useEffect() 而没有其他任何内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码:

import React, { useState, useEffect } from "react";function callSearchApi(userName: string, searchOptions: SearchOptions, searchQuery: string): Promise{返回新的承诺((解决,拒绝)=> {常量搜索结果 =searchOptions.fooOption?[Foo 1"、Foo 2"、Foo 3"]:[小节1",小节2"]setTimeout(()=>resolve(searchResult), 3000)})}输入搜索选项 = {fooOption:布尔值}类型搜索结果 = 字符串[]导出类型 SearchPageProps = {用户名:字符串}导出函数 SearchPage(props: SearchPageProps) {const [isSearching, setIsSearching] = useState(false)const [searchResult, setSearchResult] = useState([])const [searchOptions, setSearchOptions] = useState({fooOption: false})const [searchQuery, setSearchQuery] = useState("")const [lastSearchButtonClickTimestamp, setLastSearchButtonClickTimestamp] = useState(Date.now())//####################useEffect(() => {setIsSearching(真)设置搜索结果([])const doSearch = () =>callSearchApi(props.userName, searchOptions, searchQuery)doSearch().then(newSearchResult => {设置搜索结果(新搜索结果)setIsSearching(false)}).catch(error => {控制台日志(错误)setIsSearching(false)})}, [lastSearchButtonClickTimestamp])//####################const handleSearchButtonClick = () =>{setLastSearchButtonClickTimestamp(Date.now())}返回 (<div><div><标签><输入类型=复选框"选中={searchOptions.fooOption}onChange={ev =>setSearchOptions({fooOption: ev.target.checked})}/>富选项

<div><输入类型=文本"值={searchQuery}占位符=搜索查询"onChange={ev =>setSearchQuery(ev.target.value)}/>

<div><button onClick={handleSearchButtonClick} disabled={isSearching}>{正在搜索?正在寻找……":搜索"}

<小时/><div><label>搜索结果:</label><输入类型=文本"只读={真}值={搜索结果}/>

)}导出默认搜索页面

另见这个Codesandbox.

代码运行良好.我可以更改文本字段中的搜索查询,然后单击选项复选框.一旦我准备好了,我可以点击搜索"按钮,然后才会通过获取数据发生副作用.

现在,问题是编译器抱怨:

<块引用>

React Hook useEffect 缺少依赖项:props.user.loginName"、searchFilter"和searchQuery".包括它们或删除依赖项数组.[react-hooks/exhaustive-deps]

但是,如果我将 props.user.loginNamesearchFiltersearchQuery 添加到依赖项列表中,那么每当我单击复选框或在文本字段中输入单个字符.

我确实了解钩子依赖的概念,但我不知道如何先输入一些数据,只有单击按钮才会触发副作用.

这方面的最佳做法是什么?我已经阅读了 https://reactjs.org/docs/hooks-effect.htmlhttps://www.robinwieruch.de/react-hooks-fetch-data 但找不到关于我的问题的任何示例.

更新 1:

我还提出了这个解决方案,如下所示:

type DoSearch = {调用:()=>Promise}导出函数 SearchPage(props: SearchPageProps) {const [isSearching, setIsSearching] = useState(false)const [searchResult, setSearchResult] = useState([])const [searchOptions, setSearchOptions] = useState({fooOption: false})const [searchQuery, setSearchQuery] = useState("")const [doSearch, setDoSearch] = useState()//####################useEffect(() => {如果(doSearch !==未定义){setIsSearching(真)设置搜索结果([])doSearch.call().then(newSearchResult => {设置搜索结果(新搜索结果)setIsSearching(false)}).catch(error => {控制台日志(错误)setIsSearching(false)})}}, [doSearch])//####################const handleSearchButtonClick = () =>{setDoSearch({call: () => callSearchApi(props.userName, searchOptions, searchQuery)})}返回 (<div>...</div>)}

现在实际的函数是唯一可以正常工作的依赖项,编译器也很满意.但是,我不喜欢的是,我需要带有 call 属性的包装器对象.如果我想直接将箭头函数传递给状态,这不会按预期工作,例如:

const [doSearch, setDoSearch] = useState<()=>Promise>()...setDoSearch(() => callSearchApi(props.userName, searchOptions, searchQuery))

doSearch 没有设置为箭头函数,但是 callSearchApi 会立即执行.有人知道为什么吗?

解决方案

您可以从效果中删除 setIsSearching(true),并在单击按钮时将其分开.

const handleSearchButtonClick = () =>{setLastSearchButtonClickTimestamp(Date.now())setIsSearching(true);}

然后,您可以像这样修改您的 useEffect 语句:

 useEffect(() => {如果(!isSearching){返回假;}设置搜索结果([])const doSearch = () =>callSearchApi(props.userName, searchOptions, searchQuery)doSearch().then(newSearchResult => {设置搜索结果(新搜索结果)setIsSearching(false)}).catch(error => {控制台日志(错误)setIsSearching(false)})}, [allYourSuggestedDependencies])//添加所有建议的依赖项

这将完成您正在寻找的内容.另一种方法是禁用 react-hooks/exhaustive-deps 规则.

如果你只需要在点击按钮时触发获取,我就使用一个函数.

useEffect 很有用,例如,当您有一个过滤器列表(切换),并且您希望每次切换一个过滤器时进行一次获取(想象一下电子商务).这是一个天真的例子,但它说明了这一点:

useEffect(() => {fetchProducts(过滤器);}, [过滤器])

I have the following code:

import React, { useState, useEffect } from "react"

function callSearchApi(userName: string, searchOptions: SearchOptions, searchQuery: string): Promise<SearchResult>{
    return new Promise((resolve, reject) => {
        const searchResult =
            searchOptions.fooOption
            ? ["Foo 1", "Foo 2", "Foo 3"]
            : ["Bar 1", "Bar 2"]
        setTimeout(()=>resolve(searchResult), 3000)
    })
}

type SearchOptions = {
    fooOption: boolean
}

type SearchResult = string[]

export type SearchPageProps = {
    userName: string
}

export function SearchPage(props: SearchPageProps) {
    const [isSearching, setIsSearching] = useState<boolean>(false)
    const [searchResult, setSearchResult] = useState<SearchResult>([])
    const [searchOptions, setSearchOptions] = useState<SearchOptions>({fooOption: false})
    const [searchQuery, setSearchQuery] = useState<string>("")
    const [lastSearchButtonClickTimestamp, setLastSearchButtonClickTimestamp] = useState<number>(Date.now())
    // ####################
    useEffect(() => {
        setIsSearching(true)
        setSearchResult([])
        const doSearch = () => callSearchApi(props.userName, searchOptions, searchQuery)
        doSearch().then(newSearchResult => {
            setSearchResult(newSearchResult)
            setIsSearching(false)
        }).catch(error => {
            console.log(error)
            setIsSearching(false)
        })
    }, [lastSearchButtonClickTimestamp])
    // ####################
    const handleSearchButtonClick = () => {
        setLastSearchButtonClickTimestamp(Date.now())
    }
    return (
        <div>
            <div>
                <label>
                    <input
                        type="checkbox"
                        checked={searchOptions.fooOption}
                        onChange={ev => setSearchOptions({fooOption: ev.target.checked})}
                    />
                    Foo Option
                </label>
            </div>
            <div>
                <input
                    type="text"
                    value={searchQuery}
                    placeholder="Search Query"
                    onChange={ev => setSearchQuery(ev.target.value)}
                />
            </div>
            <div>
                <button onClick={handleSearchButtonClick} disabled={isSearching}>
                    {isSearching ? "searching..." : "Search"}
                </button>
            </div>
            <hr/>
            <div>
                <label>Search Result: </label>
                <input
                    type="text"
                    readOnly={true}
                    value={searchResult}
                />
            </div>
        </div>
    )
}

export default SearchPage

also see this Codesandbox.

The code works fine. I can change the search query in the text field and click the option checkbox. Once, I am ready, I can click the "Search" button and only then the side effect occurs by fetching the data.

Now, the problem is that the compiler complains about:

React Hook useEffect has missing dependencies: 'props.user.loginName', 'searchFilter', and 'searchQuery'. Either include them or remove the dependency array. [react-hooks/exhaustive-deps]

However, if I add props.user.loginName, searchFilter and searchQuery to the dependency list, then the side effect is triggered whenever I click the checkbox or type a single character in the text field.

I do understand the concept of hook dependencies, but I don't know how to first enter some data and only with a button click trigger the side effect.

What is the best practice for this? I have read both https://reactjs.org/docs/hooks-effect.html and https://www.robinwieruch.de/react-hooks-fetch-data but couldn't find any example concerning my question.

Update 1:

I have also come up with this solution which looks like:

type DoSearch = {
    call: ()=>Promise<SearchResult>
}

export function SearchPage(props: SearchPageProps) {
    const [isSearching, setIsSearching] = useState<boolean>(false)
    const [searchResult, setSearchResult] = useState<SearchResult>([])
    const [searchOptions, setSearchOptions] = useState<SearchOptions>({fooOption: false})
    const [searchQuery, setSearchQuery] = useState<string>("")
    const [doSearch, setDoSearch] = useState<DoSearch>()

    // ####################
    useEffect(() => {
        if(doSearch !==undefined){
            setIsSearching(true)
            setSearchResult([])
            doSearch.call().then(newSearchResult => {
                setSearchResult(newSearchResult)
                setIsSearching(false)
            }).catch(error => {
                console.log(error)
                setIsSearching(false)
            })
        }
    }, [doSearch])
    // ####################
    const handleSearchButtonClick = () => {
        setDoSearch({call: () => callSearchApi(props.userName, searchOptions, searchQuery)})
    }
    return (<div>...</div>)
}

Now the actual function is the only dependency which works fine and the compiler is happy, too. However, what I do not like, is that fact that I need that wrapper object with the call property. If I want to pass an arrow function directly to the state, this does not work as expected, e.g.:

const [doSearch, setDoSearch] = useState<()=>Promise<SearchResult>>()
...
setDoSearch(() => callSearchApi(props.userName, searchOptions, searchQuery))

The doSearch is not set to the arrow function, but callSearchApi is executed straight away. Does anybody know why?

解决方案

You could remove setIsSearching(true) from your effect, and set it apart when you click your button.

const handleSearchButtonClick = () => {
        setLastSearchButtonClickTimestamp(Date.now())
        setIsSearching(true);
    }

Then, you can modify your useEffect statement like this:

    useEffect(() => {
        if(!isSearching) {
           return false;
        }
        setSearchResult([])
        const doSearch = () => callSearchApi(props.userName, searchOptions, searchQuery)
        doSearch().then(newSearchResult => {
            setSearchResult(newSearchResult)
            setIsSearching(false)
        }).catch(error => {
            console.log(error)
            setIsSearching(false)
        })
    }, [allYourSuggestedDependencies]) // add all the suggested dependencies

This will accomplish what you are looking for. Another way would be just disabling the react-hooks/exhaustive-deps rule.

If you just need to trigger the fetch only when the button is clicked, I'd just use a function.

useEffect is useful for instance, when you have a list of filters (toggles), and you want to make a fetch every time you toggle one filter (imagine an e-commerce). This is a naive example, but it makes the point:

useEffect(() => {
   fetchProducts(filters);
}, [filters])

这篇关于如何仅在按钮单击时触发具有多个依赖项的 useEffect() 而没有其他任何内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆