reactjs 如何仅在按钮单击时触发具有多个依赖项的useEffect(),而不触发其他依赖项

zvokhttg  于 2023-06-22  发布在  React
关注(0)|答案(4)|浏览(119)

我有以下代码:

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

参见this Codesandbox
代码工作正常。我可以在文本字段中更改搜索查询并单击选项复选框。一旦我准备好了,我可以点击“搜索”按钮,只有这样,副作用才发生,获取数据。
现在,问题是编译器会抱怨:
React Hook useEffect缺少依赖项:'props.user. loginName'、'searchFilter'和' searchQuery'。包括它们或删除依赖项数组。[react-hooks/extive-deps]
但是,如果我将props.user.loginNamesearchFiltersearchQuery添加到依赖项列表中,那么每当我单击复选框或在文本字段中键入单个字符时,就会触发副作用。
我确实理解钩子依赖的概念,但我不知道如何先输入一些数据,然后只用一个按钮点击触发副作用。
这方面的最佳做法是什么?我读过https://reactjs.org/docs/hooks-effect.htmlhttps://www.robinwieruch.de/react-hooks-fetch-data,但找不到任何关于我问题的例子。

更新1:

我还提出了this solution,它看起来像:

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>)
}

现在,实际的函数是唯一工作正常的依赖项,编译器也很高兴。然而,我不喜欢的是,我需要具有call属性的 Package 器对象。如果我想直接传递一个箭头函数给状态,这不会像预期的那样工作,例如:

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

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

agyaoht7

agyaoht71#

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

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

然后,你可以修改你的useEffect语句,如下所示:

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

这将实现你正在寻找的东西。另一种方法是禁用react-hooks/exhaustive-deps规则。
如果你只需要在点击按钮时触发fetch,我只需要使用一个函数。
useEffect是有用的,例如,当你有一个过滤器列表(切换),你想每次切换一个过滤器(想象一个电子商务)进行提取。这是一个幼稚的例子,但它说明了一点:

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

8mmmxcuj2#

这就是useEffect应该是这样的。

  • props.userName在依赖列表中是有意义的,因为我们肯定希望在userName更改时获取新数据。
  • searchOptionssearchQuery当你遇到这种情况时,最好使用reducer,所以你只需要调度action ==> searchOptionssearchQuery就不会在userEffect里面了。这篇文章从丹阿布拉莫夫提供了深入的解释,他们简单的例子实现它

我用useReducer快速转换你的例子,请看一下

import React, { useState, useEffect, useReducer } 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);
  });
}

const initialState = {
  searchOptions: { fooOption: false },
  searchQuery: "",
  startSearch: false, // can replace searching
  searchResult: []
};

const reducer = (state: any, action: any) => {
  switch (action.type) {
    case "SEARCH_START":
      return { ...state, startSearch: true, setSearchResult: [] }; //setSearchResult: [] base on your example
    case "SEARCH_SUCCESS":
      return { ...state, setSearchResult: action.data, startSearch: false };
    case "SEARCH_FAIL":
      return { ...state, startSearch: false };
    case "UPDATE_SEARCH_OPTION":
      return { ...state, searchOptions: { fooOption: action.data } };
    case "UPDATE_SEARCH_QUERY":
      return { ...state, searchQuery: action.data };
    default:
      return state;
  }
};

function SearchPage(props: SearchPageProps) {
  const [
    lastSearchButtonClickTimestamp,
    setLastSearchButtonClickTimestamp
  ] = useState<number>(Date.now());
  const [state, dispatch] = useReducer(reducer, initialState);
  const { searchOptions, startSearch, searchQuery, searchResult } = state;
  // ####################
  useEffect(() => {
    dispatch({ type: "SEARCH_START" });
  }, [lastSearchButtonClickTimestamp]);
  // ####################
  const handleSearchButtonClick = () => {
    setLastSearchButtonClickTimestamp(Date.now());
  };

  if (startSearch) {
    callSearchApi(props.userName, searchOptions, searchQuery)
      .then(newSearchResult => {
        dispatch({ type: "SEARCH_SUCCESS", data: newSearchResult });
      })
      .catch(error => {
        console.log(error);
        dispatch({ type: "SEARCH_FAIL" });
      });
  }
  return (
    <div>
      <div>
        <label>
          <input
            type="checkbox"
            checked={searchOptions.fooOption}
            onChange={ev =>
              dispatch({
                type: "UPDATE_SEARCH_OPTION",
                data: ev.target.checked
              })
            }
          />
          Foo Option
        </label>
      </div>
      <div>
        <input
          type="text"
          value={searchQuery}
          placeholder="Search Query"
          onChange={ev =>
            dispatch({ type: "UPDATE_SEARCH_QUERY", data: ev.target.value })
          }
        />
      </div>
      <div>
        <button onClick={handleSearchButtonClick} disabled={startSearch}>
          {startSearch ? "searching..." : "Search"}
        </button>
      </div>
      <hr />
      <div>
        <label>Search Result: </label>
        <input type="text" readOnly={true} value={searchResult} />
      </div>
    </div>
  );
}

export default SearchPage;

Codesandbox for that

h9vpoimq

h9vpoimq3#

使用最新的React,你可以简单地用途:
定义所有依赖项并使用if语句检查可用性:

useEffect(() => {
  if (router.isReady && currentUser && id) {
    console.log('current user:', currentUser.uid);
    const user_role = getUserRole(id, currentUser.uid);
  }
}, [router.isReady, currentUser, id]);
bttbmeg0

bttbmeg04#

副作用不是为了这个。但是如果你想在变量发生变化时执行useEffect,你可以把它放在依赖数组中。
示例

function effect() {
       let [n, setN] = useState('')
       useEffect(() => {
            //some api need to call when 'n' value updated everytime.
         }, [n])

        //update the N variable with ur requirement
         const updateN = (val) => {
            setN(val)
          }
  }

希望这个能帮上忙

相关问题