如何仅在单击按钮时就触发具有多个依赖项的useEffect()

时间:2020-06-26 13:23:28

标签: reactjs typescript react-hooks

我有以下代码:

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”。包括它们或删除依赖项数组。 [反应钩/穷尽滴水]

但是,如果我在依赖项列表中添加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属性的包装对象。 如果我想将箭头函数直接传递给状态,则无法按预期方式运行,例如:

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

doSearch未设置为箭头功能,但立即执行了callSearchApi。有人知道为什么吗?

3 个答案:

答案 0 :(得分:1)

您可以从效果中删除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规则。

如果只需要在单击按钮时才触发获取,那么我将使用一个函数。

useEffect很有用,例如,当您有一个过滤器列表(切换),并且每次切换一个过滤器(想像一个电子商务)时都希望进行提取。这是一个幼稚的例子,但它指出了重点:

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

答案 1 :(得分:1)

那应该是useEffect。

  • props.userName在依赖列表中很有意义,因为我们肯定要在更改userName时获取新数据。
  • searchOptionssearchQuery遇到这种情况时,最好使用reducer,因此只需要分派action ==> searchOptionssearchQuery就不会了在userEffect内部。 This article from Dan Abramov 提供了深入的解释,并且提供了实现它的简单示例

我将使用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

答案 2 :(得分:-1)

副作用并不意味着那样。但是,如果要在变量发生更改时执行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)
          }
  }

希望这会有所帮助