解决在 useEffect 中使用自定义钩子的方法? [英] Work around to use custom hook in useEffect?

查看:46
本文介绍了解决在 useEffect 中使用自定义钩子的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

Main.tsx:

import React, {useState, useEffect} from 'react';从 './hooks/useApi' 导入 Api;从反应模式"导入模态从 './Vim' 导入 Vim;导入'./Main.css';导入'./modal.css';Modal.setAppElement('#root')函数 Main():JSX.Element {const [postId,updatePostId] = useState(null)const [content, updateContent] = useState('default text');const [auth, updateAuth] = useState(false)const [authModalIsOpen, setAuthModal] = useState(false)const [username, setUsername] = useState('')const [密码,setPassword] = useState('')const [authKey, setAuthKey] = useState('')常量[refreshKey,setRefreshKey] = useState( 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYxMjMzNjU4MiwianRpIjoiZTA0YjRlMjQ3MTI2NGY5ZWE4MWRiZjdiYmUzYzYwNzkiLCJ1c2VyX2lkIjoxfQ.TFBBqyZH8ZUtOLy3N-iwikXOLi2x_eKmdZuCVafPWgc')const apiUrl = 'http://127.0.0.1:8000/'函数 openAuthModal(){ setAuthModal(true) }函数 closeAuthModal(){if(auth){ setAuthModal(false) }}useEffect(()=>{const 道具 = {用户名:'乌鸦',密码:'asdfsdfds',有效载荷:{路径:'注释/',方法:'获取',正文:{pid:postId},},完整:(res:{})=>{console.log(res)},失败:()=>{}}api(道具)},[postId])函数 loadPost(pid:number):string|null{//从 API 获取,加载帖子内容console.log('我可以访问:'+postId)返回空;}函数 backLinks():JSX.Element{返回(<div className="反向链接">

)}函数发送登录(){const requestOptions = {方法:'POST',标头:{'内容类型':'应用程序/json'},正文:JSON.stringify({用户名:用户名,密码:密码})}return fetch(apiUrl+'login', requestOptions).then(response=>response.json())}返回 (

<Vim key={postId} content={content}/><模态isOpen={authModalIsOpen}onRequestClose={closeAuthModal}类名='模态'覆盖类名称='覆盖'><form onSubmit={(e)=>{e.preventDefault()控制台日志(用户名)sendLogin().then((data)=>{如果(数据.认证){更新验证(真)}})}}><input name='username' onChange={(e)=>{设置用户名(e.target.value)}}/><输入类型=密码"name='password' onChange={(e)=>{setPassword(e.target.value)}}/><button type="submit">登录</button></表单></模态>

)}导出默认主

useApi.tsx:

import {useState, useEffect} from 'react'接口 IProps {用户名:字符串,密码:字符串,有效载荷:IPayload,完成:(结果:{})=>void,失败:()=>void}接口 IPayload {路径:字符串,方法:字符串,正文:{}|null,}函数API(道具:IProps){const [accessKey, setAccessKey] = useState('')const [refreshKey, setRefreshKey] = useState('')const [refreshKeyIsValid, setRefreshKeyIsValid] = useState(null)const apiUrl = 'http://127.0.0.1:8000/api/'const [accessKeyIsValid, setAccessKeyIsValid] = useState(null)const [results, setResults] = useState(null)功能去(有效载荷=道具.有效载荷){常量选项 = {方法:payload.method,标题:{'内容类型':'应用程序/json','授权':'承载'+访问密钥,},... (payload.body !== null) &&{ 正文:JSON.stringify(payload.body) }}返回获取(apiUrl+payload.path,options).then(响应=>{如果(响应.状态===401){setAccessKeyIsValid(false)返回假} 别的 {返回 response.json().then(响应=>{设置结果(响应)返回真})}})}useEffect(()=>{如果(结果){props.complete(结果)}},[结果])useEffect(()=>{if(accessKeyIsValid===false){//我们试图发出请求,但我们的密钥无效.//我们需要使用刷新键常量选项 = {方法:'POST',标头:{'内容类型':'应用程序/json',},正文:JSON.stringify( {'refresh': refreshKey} ),}获取(apiUrl+'令牌/刷新/',选项).then(响应=>{如果(响应.状态 === 401){setRefreshKeyIsValid(false)//这需要触发登录事件} 别的 {响应.json().then(响应=>{setRefreshKeyIsValid(true)setAccessKey(response.access)setRefreshKey(response.refresh)setAccessKeyIsValid(true)})}})}},[accessKeyIsValid])useEffect(()=>{if(accessKeyIsValid===true){//刚刚使用新的访问密钥刷新.再次尝试我们的请求去()}},[accessKeyIsValid])useEffect(()=>{if(refreshKeyIsValid===false){//即使尝试登录后,RK 也是无效的//我们必须直接登录.常量选项 = {方法:'POST',标头:{'内容类型':'应用程序/json'},正文:JSON.stringify({用户名:props.username,密码: props.password,})}获取(apiUrl+'api/token/',选项).then(响应=>{if(response.status === 401){ props.fail() }别的 {响应.json().then(响应=>{setAccessKey(response.access)setAccessKeyIsValid(true)})}})}},[refreshKeyIsValid])返回(去())};导出默认 Api

解决方案

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

function useApi(props: IProps, deps) {//<-- 接受一个依赖数组argconst [accessKey, setAccessKey] = useState("");const [refreshKey, setRefreshKey] = useState("");const [refreshKeyIsValid, setRefreshKeyIsValid] = useState(空值);const apiUrl = "http://127.0.0.1:8000/api/";const [accessKeyIsValid, setAccessKeyIsValid] = useState(空值);const [results, setResults] = useState(空);const go = useCallback(() => {//<-- 记住 go 回调const { body, method, path } = props.payload;常量选项 = {方法,标题:{内容类型":应用程序/json",授权:承载"+ 访问密钥},...(body !== null && { body: JSON.stringify(body) })};return fetch(apiUrl + path, options).then((response) => {如果(响应.状态 === 401){setAccessKeyIsValid(false);返回假;} 别的 {返回 response.json().then((response) => {设置结果(响应);返回真;});}});}, [accessKey, props.payload, setAccessKeyIsValid, setResults]);useEffect(() => {如果(结果){props.complete(结果);}}, [结果, 道具]);useEffect(() => {如果(accessKeyIsValid){//刚刚使用新的访问密钥刷新.再次尝试我们的请求去();} 别的 {//我们试图发出请求,但我们的密钥无效.//我们需要使用刷新键常量选项 = {方法:POST",标头:{内容类型":应用程序/json";},正文:JSON.stringify({刷新:refreshKey })};fetch(apiUrl + "token/refresh/", options).then((response) => {如果(响应.状态 === 401){setRefreshKeyIsValid(false);//这需要触发登录事件} 别的 {response.json().then((response) => {setRefreshKeyIsValid(true);setAccessKey(response.access);setRefreshKey(response.refresh);setAccessKeyIsValid(true);});}});}}, [accessKeyIsValid, ...deps]);//<-- 传递您的依赖项useEffect(() => {如果(!refreshKeyIsValid){//即使尝试登录后,RK 也是无效的//我们必须直接登录.常量选项 = {方法:POST",标头:{内容类型":应用程序/json";},正文:JSON.stringify({用户名:props.username,密码:props.password})};fetch(apiUrl + "api/token/", options).then((response) => {如果(响应.状态 === 401){props.fail();} 别的 {response.json().then((response) => {setAccessKey(response.access);setAccessKeyIsValid(true);});}});}}, [refreshKeyIsValid, ...deps]);//<-- 传递您的依赖项返回去();}

用法

useApi(props, [postId]);

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]);

这篇关于解决在 useEffect 中使用自定义钩子的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆