I recently heard about React.Suspense and I wanted to use it in a new project.
To setup React.Suspense with Axios I followed this tutorial
The first request of in that project consist of checking if there is a user logged in to the app.
I checked that by sending a request /user to the API and if the API returns the user information if there is a cookie containing a JWT token otherwise it will return a 401 error
The issue is that I am having errors that I don't understand.
Here is the code of some parts of the project, hoping that it can help you understand my issue
App.tsx
function App() {
return (
<BrowserRouter>
<AxiosInstanceProvider config={{ baseURL: process.env.BACKEND_URL || "http://localhost:8080/" }}>
<CurrentUserContextProvider>
<Routes>
<Route path='/' element={<LandingPage />} />
<Route path='/auth/*' element={<AuthPage />}>
<Route path='sign-in' element={<SignInPage />} />
<Route path='sign-up' element={<SignUpPage />} />
</Route>
<Route path='/orgs' element={<ListOfOrganizationsPage />} />
</Routes>
</CurrentUserContextProvider>
</AxiosInstanceProvider>
</BrowserRouter>
);
}
AxiosInstanceProvider
export const AxiosContext = createContext<AxiosInstance | null>(null);
export const useAxiosContext = () => useContext(AxiosContext);
interface AxiosInstanceProviderProps {
config: CreateAxiosDefaults | AxiosRequestConfig,
children: JSX.Element
}
export const AxiosInstanceProvider = ({
config = {},
children,
}: AxiosInstanceProviderProps) => {
const instanceRef = useRef(
axios.create({
...config,
headers: {
'Accept': 'application/json',
"Content-Type": "application/json",
},
withCredentials: true
})
);
const location = useLocation();
const navigate = useNavigate();
const redirectToSignIn = useCallback(() => {
if (location.pathname.includes("/auth/sign-in")) return;
navigate(`/auth/sign-in?from=${encodeURIComponent(location.pathname)}`)
}, [location.pathname, navigate]);
const onResponse = useCallback((response: AxiosResponse) => {
return response;
}, []);
const onUnauthorizedRequest = useCallback((error: AxiosError) => {
if (error?.response?.status === 401) {
redirectToSignIn();
return;
}
return Promise.reject(error)
}, [redirectToSignIn]);
useEffect(() => {
instanceRef.current.interceptors.response.use(onResponse, onUnauthorizedRequest);
}, [instanceRef, onResponse, onUnauthorizedRequest]);
return (
<AxiosContext.Provider value={instanceRef.current}>
{children}
</AxiosContext.Provider>
);
}
useAxios
type RequestMethod = "GET" | "POST" | "PATCH" | "DELETE";
export class TError {
error: string;
constructor(message: string) {
this.error = message;
}
}
function wrapPromise<T>(promise: Promise<T>) {
let status = 'pending'
let response: T|string;
const suspender = promise.then(
(res: T) => {
status = 'success'
response = res
},
).catch((err: AxiosError) => {
console.log("an error occurent: ", err);
status = 'error'
response = err.response?.data as string;
},
)
const read = () => {
switch (status) {
case 'pending':
throw suspender
case 'error':
throw response
default:
return response
}
}
return { read }
}
const useAxios = () => {
const abortControllerRef = useRef(new AbortController());
const contextAxiosInstance = useAxiosContext();
const instance = useMemo(() => {
return contextAxiosInstance || axios
}, [contextAxiosInstance])
const cancel = () => {
abortControllerRef.current.abort();
}
const fetchApiResponse = useCallback(
async <T,S = {}>(url: string, method: RequestMethod, data?: S) => {
try {
const response = await instance<T>({
method,
url,
data
})
if (response.status >= 200 && response.status < 400) {
return response;
} else {
throw new Error(response.statusText)
}
} catch (error) {
return error as AxiosError;
}
},
[instance]
)
const fetchApiData = useCallback(<T,S = {}>(url: string, method: RequestMethod, data?: S) => {
const promise = instance<T>({
method,
url,
data
})
.then((response) => response.data);
return wrapPromise(promise);
},
[instance]
)
return useMemo(() => ({
cancel,
fetchApiResponse,
fetchApiData
}), [fetchApiData, fetchApiResponse]);
};
export default useAxios;
GetCurrentUser
export default function useApiAuth() {
const { fetchApiData } = useAxios();
const getCurrentUser = useCallback(
() => fetchApiData<UserType>("user", "GET").read(),
[fetchApiData]
)
return useMemo(
() => ({
getCurrentUser,
}),
[
getCurrentUser
]
)
}
CurrentUserContext
interface CurrentUserContextType {
currentUser: UserType | null;
isLoadingCurrentUser: boolean;
setCurrentUser: (user: UserType) => void;
}
export const CurrentUserContext = createContext<CurrentUserContextType>({
currentUser: null,
isLoadingCurrentUser: false,
setCurrentUser: () => {}
});
export const useCurrentUserContext = () => useContext(CurrentUserContext);
const CurrentUserContextProvider = ({ children }: CurrentUserContextProviderProps) => {
const {currentUser, isLoadingCurrentUser, setCurrentUser} = useCurrentUser();
return (
<CurrentUserContext.Provider value={{currentUser, isLoadingCurrentUser, setCurrentUser}}>
{children}
</CurrentUserContext.Provider>
)
}
interface CurrentUserContextProviderProps {
children: JSX.Element
}
export default memo(({ children }: CurrentUserContextProviderProps) => {
return (
// <ErrorBoundary fallback={<div>Error Occurred when checking current user..</div>}>
<Suspense fallback={<div>Checking current user ...</div>}>
<CurrentUserContextProvider>
{children}
</CurrentUserContextProvider>
</Suspense>
// </ErrorBoundary>
)
});
useCurrentUser
export default function useCurrentUser() {
const [error, setError] = useState("");
const [currentUser, setCurrentUser] = useState<UserType | null>(null);
const [isLoadingCurrentUser, setIsLoadingCurrentUser] = useState<boolean>(true);
const { getCurrentUser } = useApiAuth();
useEffect(() => {
function _getCurrentUser() {
setIsLoadingCurrentUser(true);
const response = getCurrentUser();
console.log("GCUR-U : Response : ", response);
if (response instanceof TError) {
console.log("Response Error")
setError(response.error);
}
setIsLoadingCurrentUser(false);
}
_getCurrentUser();
}, [getCurrentUser, setIsLoadingCurrentUser, setError]);
return useMemo(() => ({
currentUser,
setCurrentUser,
isLoadingCurrentUser,
error
}), [currentUser, error, isLoadingCurrentUser]);
}
I was expecting to see the message Checking current user ... while sending the request /user to the API and then being redirect to /auth/sign-in
But I am having those errors
- Promise
{<pending>} - The above error occurred in the
