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.