Skip to content

Commit d4300dc

Browse files
authored
refactor: extract fetching to useApi hook (freeCodeCamp-2025-Summer-Hackathon#81)
1 parent 5791240 commit d4300dc

File tree

2 files changed

+100
-29
lines changed

2 files changed

+100
-29
lines changed
Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
1-
import { useCallback, useEffect, useState } from 'react';
1+
import { useEffect } from 'react';
2+
import { useApi } from '../hooks/useApi';
23
import { Link } from 'react-router';
34

45
const IdeaFormSection = ({ count }) => {
5-
let [isLoading, setLoading] = useState(true);
6-
let [error, setError] = useState(null);
7-
let [data, setData] = useState([]);
8-
9-
const fetchIdeas = useCallback(async () => {
10-
setError(null);
11-
setLoading(true);
12-
try {
13-
const response = await fetch(
14-
import.meta.env.VITE_API_LOCATION + `/ideas/?limit=${count}`
15-
);
16-
17-
if (!response.ok) {
18-
throw new Error(response.statusText);
19-
}
20-
21-
setLoading(false);
22-
setData((await response?.json())?.data);
23-
} catch (e) {
24-
console.error(e);
25-
setError(e);
26-
}
27-
}, [count]);
6+
const { isLoading, error, data, fetchFromApi } = useApi({
7+
loadingInitially: true,
8+
});
289

2910
useEffect(() => {
30-
fetchIdeas();
31-
}, [fetchIdeas]);
11+
fetchFromApi(`/ideas/?limit=${count}`);
12+
}, [count, fetchFromApi]);
3213

3314
return (
3415
<section className='idea-form-section'>
@@ -40,9 +21,9 @@ const IdeaFormSection = ({ count }) => {
4021
'Loading...'
4122
) : (
4223
<ul className='idea-list'>
43-
{data.length === 0
24+
{data?.data?.length === 0
4425
? "There's no ideas, add yours!"
45-
: data.map(({ id, name, upvoted_by }) => {
26+
: data?.data.map(({ id, name, upvoted_by }) => {
4627
return (
4728
<Link to={`/ideas/${id}`} key={id}>
4829
<li className='idea-item'>
@@ -72,4 +53,4 @@ const IdeaFormSection = ({ count }) => {
7253
);
7354
};
7455

75-
export default IdeaFormSection; //
56+
export default IdeaFormSection;

frontend/src/hooks/useApi.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { useCallback, useState } from 'react';
2+
3+
const getCrsfToken = async () => {
4+
const token = sessionStorage.getItem('csrf_token');
5+
if (token) {
6+
return token;
7+
}
8+
9+
try {
10+
const response = await fetch(
11+
import.meta.env.VITE_API_LOCATION + '/csrf/get-token'
12+
);
13+
14+
if (!response.ok) {
15+
throw new Error(response.statusText);
16+
}
17+
const fresh_token = await response.json()?.csrf_token;
18+
sessionStorage.setItem('csrf_token', fresh_token);
19+
return fresh_token;
20+
} catch (e) {
21+
console.error(e);
22+
}
23+
};
24+
25+
export const useApi = ({ method = 'GET', loadingInitially = false }) => {
26+
let [isLoading, setLoading] = useState(loadingInitially);
27+
let [error, setError] = useState(null);
28+
let [data, setData] = useState(null);
29+
let [response, setResponse] = useState(null);
30+
31+
const attachDefaultHeaders = useCallback(async options => {
32+
if (!['GET', 'HEAD', 'OPTIONS'].includes(options?.method)) {
33+
const csrf_token = await getCrsfToken();
34+
if (csrf_token) {
35+
options['headers'] = {
36+
'X-CSRF-TOKEN': csrf_token,
37+
...options?.headers,
38+
};
39+
}
40+
}
41+
42+
const access_token = sessionStorage.getItem('access_token');
43+
if (access_token) {
44+
options['headers'] = {
45+
Authorization: `Bearer ${access_token}`,
46+
...options?.headers,
47+
};
48+
}
49+
50+
return options;
51+
}, []);
52+
53+
// TODO handle responses when access_token is no longer valid -> refreshing it with refresh_token
54+
const fetchFromApi = useCallback(
55+
async (path = '', extraFetchOptions = {}) => {
56+
setError(null);
57+
setLoading(true);
58+
setResponse(null);
59+
setData(null);
60+
try {
61+
const res = await fetch(
62+
import.meta.env.VITE_API_LOCATION + path,
63+
attachDefaultHeaders({
64+
method,
65+
...extraFetchOptions,
66+
})
67+
);
68+
69+
setResponse(res);
70+
if (!res.ok) {
71+
throw new Error(res.statusText);
72+
}
73+
setData(await res?.json());
74+
} catch (e) {
75+
console.error('error');
76+
setError(e);
77+
}
78+
setLoading(false);
79+
},
80+
[attachDefaultHeaders, method]
81+
);
82+
83+
return {
84+
isLoading,
85+
error,
86+
data,
87+
response,
88+
fetchFromApi,
89+
};
90+
};

0 commit comments

Comments
 (0)