如何在单击React Hooks方式上发送请求?

时间:2019-04-12 08:16:51

标签: reactjs react-hooks

如何在按钮单击时使用react hooks发送http请求?或者,就此而言,如何在单击按钮时产生任何副作用?

到目前为止,我看到的是具有“间接”的东西,例如:

export default = () => {
  const [sendRequest, setSendRequest] = useState(false);

  useEffect(() => {
    if(sendRequest){
       //send the request
       setSendRequest(false);
    }
  },
  [sendRequest]);

  return (
    <input type="button" disabled={sendRequest} onClick={() => setSendRequest(true)}
  );
}

这是正确的方法还是其他模式?

6 个答案:

答案 0 :(得分:5)

export default () => {
  const [isSending, setIsSending] = useState(false)
  const sendRequest = useCallback(async () => {
    // don't send again while we are sending
    if (isSending) return
    // update state
    setIsSending(true)
    // send the actual request
    await API.sendRequest()
    // once the request is sent, update state again
    setIsSending(false)
  }, [isSending]) // update the callback if the state changes

  return (
    <input type="button" disabled={sendRequest} onClick={sendRequest} />
  )
}

这就是当您要在单击时发送请求并在发送时禁用按钮的情况下的含义

答案 1 :(得分:2)

您可以像在状态一样定义布尔值,一旦触发请求,将其设置为true,当收到响应时,将其设置回false

const [requestSent, setRequestSent] = useState(false);

const sendRequest = () => {
  setRequestSent(true);
  fetch().then(() => setRequestSent(false));
};

Working example

答案 2 :(得分:2)

您不需要在单击按钮时发送请求的效果,而是您所需要的只是一个处理程序方法,可以使用useCallback方法进行优化

const App = (props) => {
   //define you app state here
   const fetchRequest = useCallback(() => {
       // Api request here
   }, [add dependent variables here]);

  return (
    <input type="button" disabled={sendRequest} onClick={fetchRequest}
  );
}

使用带有useEffect的变量来跟踪请求不是正确的模式,因为您可以将状态设置为使用useEffect来调用api,但是由于其他一些更改而导致的其他渲染将导致请求进入循环

答案 3 :(得分:1)

您可以像在问题中所做的那样,获取数据作为某种状态变化的结果,但是您也可以像在类组件中那样,直接在点击处理程序中获取数据。

示例

const { useState } = React;

function getData() {
  return new Promise(resolve => setTimeout(() => resolve(Math.random()), 1000))
}

function App() {
  const [data, setData] = useState(0)

  function onClick() {
    getData().then(setData)
  }

  return (
    <div>
      <button onClick={onClick}>Get data</button>
      <div>{data}</div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

答案 4 :(得分:1)

在函数式编程中,任何异步函数都应视为副作用。

处理副作用时,您需要将启动副作用的逻辑和该副作用的结果逻辑分开(类似于redux saga)。

基本上,按钮职责只是触发副作用,而副作用职责是更新dom。

此外,由于react正在处理组件,因此您需要确保在任何setState之前或之后的每个await之后仍要挂载组件,这取决于您自己的偏好。

要解决此问题,我们可以创建一个自定义钩子useIsMounted,该钩子将使我们易于检查组件是否仍已安装

/**
 * check if the component still mounted
 */
export const useIsMounted = () => {
  const mountedRef = useRef(false);
  const isMounted = useCallback(() => mountedRef.current, []);

  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  });

  return isMounted;
};

然后您的代码应如下所示

export const MyComponent = ()=> {
  const isMounted = useIsMounted();
  const [isDoMyAsyncThing, setIsDoMyAsyncThing] = useState(false);

  // do my async thing
  const doMyAsyncThing = useCallback(async () => {
     // do my stuff
  },[])

  /**
   * do my async thing effect
  */
  useEffect(() => {
    if (isDoMyAsyncThing) {
      const effect = async () => {
        await doMyAsyncThing();
        if (!isMounted()) return;
        setIsDoMyAsyncThing(false);
      };
      effect();
    }
  }, [isDoMyAsyncThing, isMounted, doMyAsyncThing]);

  return (
     <div> 
        <button disabled={isDoMyAsyncThing} onClick={()=> setIsDoMyAsyncThing(true)}>
          Do My Thing {isDoMyAsyncThing && "Loading..."}
        </button>;
     </div>
  )
}

注意:最好将副作用的逻辑与触发副作用的逻辑(useEffect分开)

答案 5 :(得分:0)

您可以创建一个自定义钩子 useApi 并返回一个函数 execute,该函数在调用时将调用 api(通常通过某个 onClick)。

useApi 钩子:

export type ApiMethod = "GET" | "POST";

export type ApiState = "idle" | "loading" | "done";

const fetcher = async (
    url: string,
    method: ApiMethod,
    payload?: string
  ): Promise<any> => {
    const requestHeaders = new Headers();
    requestHeaders.set("Content-Type", "application/json");
  
    console.log("fetching data...");
    const res = await fetch(url, {
      body: payload ? JSON.stringify(payload) : undefined,
      headers: requestHeaders,
      method,
    });
  
    const resobj = await res.json();
    return resobj;
  };

export function useApi(
  url: string,
  method: ApiMethod,
  payload?: any
): {
  apiState: ApiState;
  data: unknown;
  execute: () => void;
} {
  const [apiState, setApiState] = useState<ApiState>("idle");

  const [data, setData] = useState<unknown>(null);
  const [toCallApi, setApiExecution] = useState(false);

  const execute = () => {
    console.log("executing now");
    setApiExecution(true);
  };


  const fetchApi = useCallback(() => {
    console.log("fetchApi called");
    fetcher(url, method, payload)
      .then((res) => {
        const data = res.data;
        setData({ ...data });
        return;
      })
      .catch((e: Error) => {
        setData(null);
        console.log(e.message);
      })
      .finally(() => {
        setApiState("done");
      });
  }, [method, payload, url]);

  // call api
  useEffect(() => {
    if (toCallApi &&  apiState === "idle") {
      console.log("calling api");
      setApiState("loading");
      fetchApi();
    }
  }, [apiState, fetchApi, toCallApi]);

  return {
    apiState,
    data,
    execute,
  };
}

在某些组件中使用 useApi

const SomeComponent = () =>{

const { apiState, data, execute } = useApi(
      "api/url",
      "POST",
      {
        foo: "bar",
      }
    );

}

if (apiState == "done") {
      console.log("execution complete",data);
}

return (
 <button
   onClick={() => {
            execute();
          }}>
Click me
</button>
);