我有一个名为 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
答案 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]);