i've seen a way in here and i came up with another solution like i explained below:
1 - i created my service with axios
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.REACT_APP_BASE_URL
// headers: { 'X-Custom-Header': 'foobar' }
});
// Add a request interceptor
instance.interceptors.request.use(
(config) => {
// Do something before request is sent
return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor
instance.interceptors.response.use(
(response) => {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
(error) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
}
);
export default instance;
2- i use that service to create a function for api call.
in here i can add a new AbortController for later use in useEffect.
import axios from 'services/request';
export function getMarket(type, callBack) {
const controller = new AbortController();
axios
.get(`https://dev.zh1.app/api/market/map?type=${type}`, {
signal: controller.signal
})
.then((res) => {
callBack(true, res.data);
})
.catch((res) => {
callBack(false, res.response);
});
return controller;
}
export default {
getMarket
};
3- in the hooks folder i created a hook called useApi. the controller from step 2 used in here. if you check the link above you can see the author add request function because you may have some props to pass to api call. i think it is valid but ugly. so i decided to create a closure for useApi to pass any params i want to the Axios in step 2.
import { useEffect, useState } from 'react';
// in useStrict mode useEffect call twice but will not in production
export default function useApi(apiFunc) {
return function useApiCall(...params) {
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const apiCall = useCallback(() => {
setLoading(true);
// controller is new AbortController which set each api function
const controller = apiFunc(...params, (ok, data) => {
setLoading(false);
if (ok) {
setResult(data);
} else {
setError(data.message);
}
});
return () => {
controller.abort();
};
}, []);
useEffect(() => {
apiCall();
}, []);
return {result, loading, error, [apiFunc.name]: apiCall};
};
}
4- finally in my react component
import { IconButton } from '@mui/material';
import useApi from '@share/hooks/useApi';
import { Refresh } from '@share/icons';
import { getCaptcha as CaptchaApi } from 'api/oauth/captcha';
import CaptchaStyle from './style';
export default function Captcha() {
const { result: captcha, getCaptcha } = useApi(CaptchaApi)();
return (
<CaptchaStyle>
<img src={`data:image/png;base64,${captcha?.base64}`} alt="captcha" />
<IconButton onClick={getCaptcha}>
<Refresh />
</IconButton>
</CaptchaStyle>
);
}
i think this approach i quite good and if you dont need to pass any props just call useApi([yourfunction])() with empty function.
and you can have access to the function inside of useApi if you need to call it again.