解决在 useEffect 中使用自定义钩子的方法?

时间:2021-02-03 01:47:56

标签: javascript reactjs typescript react-hooks

我有一个名为 Api 的自定义钩子,它从我的 API 获取数据并处理我的身份验证令牌和刷新令牌。

在我的主应用程序中,有多种方式可以改变我的状态变量“postId”。每当它发生变化时,我都希望我的 API 为它获取新内容。但是我无法在 useEffect 中调用我的自定义 Api,这就是我检测 postId 变化的方式。

有人可以建议一个解决方法吗?我花了很长时间做这个API,现在我觉得我什至无法使用它。

Main.tsx:

import React, {useState, useEffect} from 'react';
import Api from './hooks/useApi';
import Modal from 'react-modal'
import Vim from './Vim';
import './Main.css';
import './modal.css';
Modal.setAppElement('#root')

function Main():JSX.Element { 

  const [postId,updatePostId] = useState<number|null>(null)
  const [content, updateContent] = useState<string>('default text');
  const [auth, updateAuth] = useState<boolean>(false)
  const [authModalIsOpen, setAuthModal] = useState(false)
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [authKey, setAuthKey] = useState('')
  const [refreshKey, setRefreshKey] = useState('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYxMjMzNjU4MiwianRpIjoiZTA0YjRlMjQ3MTI2NGY5ZWE4MWRiZjdiYmUzYzYwNzkiLCJ1c2VyX2lkIjoxfQ.TFBBqyZH8ZUtOLy3N-iwikXOLi2x_eKmdZuCVafPWgc')

  const apiUrl = 'http://127.0.0.1:8000/'

  function openAuthModal(){ setAuthModal(true) }
  function closeAuthModal(){
    if(auth){ setAuthModal(false) }
  }

  useEffect(()=>{
    const props = {
      username: 'raven',
      password: 'asdfsdfds',
      payload: {
        path: 'notes/',
        method: 'GET',
        body: {pid: postId},
      },
      complete: (res:{})=>{console.log(res)},
      fail: ()=>{}
    }

    Api(props)
  },[postId])


  function loadPost(pid:number):string|null{
    // fetch from API, load post content
    console.log('I can access:'+postId)
    return null;
  }
  
  function backLinks():JSX.Element{
    
    return(
      <div className="backlinks">
      </div>
    )
  }
  
  function sendLogin(){
    const requestOptions = {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({
        username: username,
        password: password
      })
    }
    return fetch(apiUrl+'login', requestOptions)
      .then(response=>response.json())
  }

  return (
    <div className='main'>
      <Vim key={postId} content={content} />

      <Modal
        isOpen={authModalIsOpen}
        onRequestClose={closeAuthModal}
        className='Modal'
        overlayClassName='Overlay'
        >
        <form onSubmit={(e)=>{
          e.preventDefault()
          console.log(username)
          sendLogin().then((data)=>{
            if(data.auth){
              updateAuth(true)
            }
          })
        }}>
            <input name='username' onChange={(e)=>{
              setUsername(e.target.value)
            }}/>
            <input type="password" name='password' onChange={(e)=>{
              setPassword(e.target.value)
            }}/>
            <button type="submit">Login</button>
          </form>
        </Modal> 
    </div>
  )
}

export default Main

useApi.tsx:

import {useState, useEffect} from 'react'

interface IProps {
    username:string,
    password:string,
    payload:IPayload,
    complete: (result:{})=>void,
    fail: ()=>void
}

interface IPayload {
    path:string,
    method:string,
    body:{}|null,
}

function Api(props:IProps){

    const [accessKey, setAccessKey] = useState('')
    const [refreshKey, setRefreshKey] = useState('')
    const [refreshKeyIsValid, setRefreshKeyIsValid] = useState<null|boolean>(null)
    const apiUrl = 'http://127.0.0.1:8000/api/'
    const [accessKeyIsValid, setAccessKeyIsValid] = useState<null|boolean>(null)
    const [results, setResults] = useState<null|{}>(null)

    function go(payload=props.payload){
        const options = {
            method: payload.method,
            headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer '+accessKey,
            },
            ... (payload.body !== null) && { body: JSON.stringify(payload.body) }
        }
        return fetch(apiUrl+payload.path,options)
        .then(response=>{
            if(response.status===401){
                setAccessKeyIsValid(false)
                return false
            } else {
                return response.json()
                .then(response=>{
                    setResults(response)
                    return true
                })
            }
        })
    }
    useEffect(()=>{
        if(results){
            props.complete(results)
        }
    },[results])

    useEffect(()=>{
        if(accessKeyIsValid===false){
            // We tried to make a request, but our key is invalid.
            // We need to use the refresh key
            const options = {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', },
                body: JSON.stringify( {'refresh': refreshKey} ),
            }
            fetch(apiUrl+'token/refresh/', options)
            .then(response=>{
                if(response.status === 401){
                    setRefreshKeyIsValid(false)
                    // this needs to trigger a login event
                } else {
                    response.json()
                    .then(response=>{
                        setRefreshKeyIsValid(true)
                        setAccessKey(response.access)
                        setRefreshKey(response.refresh)
                        setAccessKeyIsValid(true)
                    })
                }
            })
        }
    },[accessKeyIsValid])

    useEffect(()=>{
        if(accessKeyIsValid===true){
            // Just refreshed with a new access key. Try our request again
            go()
        }
    },[accessKeyIsValid])

    useEffect(()=>{
        if(refreshKeyIsValid===false){
            // even after trying to login, the RK is invalid
            // We must straight up log in.
            const options = {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    username: props.username,
                    password: props.password,
                 })
            }
            fetch(apiUrl+'api/token/', options)
            .then(response=>{
                if(response.status === 401){ props.fail() }
                else { 
                    response.json()
                    .then(response=>{
                        setAccessKey(response.access)
                        setAccessKeyIsValid(true)
                    })
                }
            })
        }

        
    },[refreshKeyIsValid])

    return( go() )
};

export default Api

1 个答案:

答案 0 :(得分:0)

您可以将依赖项传递给您的自定义钩子,以传递给可能依赖它们的任何底层钩子。由于我对 Typescript 不是很熟悉,因此可能需要进行一些必要的类型定义调整。我已经查看了您的钩子逻辑,并就我认为 postId 更改时正确的依赖项提出了以下建议。

function useApi(props: IProps, deps) { // <-- accept a dependency array arg
  const [accessKey, setAccessKey] = useState("");
  const [refreshKey, setRefreshKey] = useState("");
  const [refreshKeyIsValid, setRefreshKeyIsValid] = useState<null | boolean>(
    null
  );
  const apiUrl = "http://127.0.0.1:8000/api/";
  const [accessKeyIsValid, setAccessKeyIsValid] = useState<null | boolean>(
    null
  );
  const [results, setResults] = useState<null | {}>(null);

  const go = useCallback(() => { // <-- memoize go callback
    const { body, method, path } = props.payload;
    const options = {
      method,
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + accessKey
      },
      ...(body !== null && { body: JSON.stringify(body) })
    };
    return fetch(apiUrl + path, options).then((response) => {
      if (response.status === 401) {
        setAccessKeyIsValid(false);
        return false;
      } else {
        return response.json().then((response) => {
          setResults(response);
          return true;
        });
      }
    });
  }, [accessKey, props.payload, setAccessKeyIsValid, setResults]);

  useEffect(() => {
    if (results) {
      props.complete(results);
    }
  }, [results, props]);

  useEffect(() => {
    if (accessKeyIsValid) {
      // Just refreshed with a new access key. Try our request again
      go();
    } else {
      // We tried to make a request, but our key is invalid.
      // We need to use the refresh key
      const options = {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refresh: refreshKey })
      };
      fetch(apiUrl + "token/refresh/", options).then((response) => {
        if (response.status === 401) {
          setRefreshKeyIsValid(false);
          // this needs to trigger a login event
        } else {
          response.json().then((response) => {
            setRefreshKeyIsValid(true);
            setAccessKey(response.access);
            setRefreshKey(response.refresh);
            setAccessKeyIsValid(true);
          });
        }
      });
    }
  }, [accessKeyIsValid, ...deps]); // <-- pass your dependencies

  useEffect(() => {
    if (!refreshKeyIsValid) {
      // even after trying to login, the RK is invalid
      // We must straight up log in.
      const options = {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          username: props.username,
          password: props.password
        })
      };
      fetch(apiUrl + "api/token/", options).then((response) => {
        if (response.status === 401) {
          props.fail();
        } else {
          response.json().then((response) => {
            setAccessKey(response.access);
            setAccessKeyIsValid(true);
          });
        }
      });
    }
  }, [refreshKeyIsValid, ...deps]); // <-- pass your dependencies

  return go();
}

用法

useApi(props, [postId]);