简体   繁体   中英

Work around to use custom hook in useEffect?

I have a custom hook called Api which fetches data from my API & handles my auth token and refresh tokens.

On my Main app, there are multiple ways that my state variable "postId" will be changed. And whenever it is changed, I want my API to fetch the new content for that. But I can't call my custom Api within useEffect, which is how I'm detecting changes in postId.

Can someone please suggest a workaround? I spent forever making this API, now I feel like I can't even use it.

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

You can pass dependencies to your custom hooks to be passed on to any underlying hooks that may depend on them. Since I'm not very familiar with Typescript there may be some necessary type definition tweaks. I've looked over your hook logic and suggest the follow for what I think would be the correct dependencies for when postId changes.

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

Usage

useApi(props, [postId]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM