I'm trying to export an axios call from an external file to my component, in useEffect. Im exporting the function and importing in the said component. The response is "undefined".
api_call.js:
import axios from 'axios';
const accessToken = window.localStorage.getItem('accessToken')
export const getPublicCircles = async () => {
const headers = {
'Content-Type': 'application/json',
'Accept-Language': 'fr',
'Authorization': `Bearer ${accessToken}`,
}
await axios.get('https://myurl.com/api/this-info', { headers })
.then(response => console.log(response))
.catch(error => console.log('error', error))
};
( I also tried with .then((response) => return response.data.data)
component.js
import * as API from '../../api/api_call';
export default function PublicCircles() {
const [circles, getCircles] = useState('');
useEffect(() => {
const fetchData = async () => {
const response = await API.getPublicCircles();
const json = await response.json();
console.log(response)
getCircles(response);
}
fetchData()
.catch(console.error);;
}, []);
return (
<Box>
{circles === '' ? null :
<PublicCircle circles={circles} />}
</Box>
)
}
Here are the results (getting the info from the api_call.js file, not the PublicCirlces.js one.
Thank you.
The real problem here is that the function getPublicCircles returns nothing, which is why any variable to which the result of this function call is assigned as a value, will be undefined per JavaScript rules, because a function that doesn't return any value will return undefined.
It's not a good idea to use async/await and then/catch in handling a promise together. Below is the example of handling it correctly with try/catch and async/await:
export const getPublicCircles = async () => {
const headers = {
'Content-Type': 'application/json',
'Accept-Language': 'fr',
'Authorization': `Bearer ${accessToken}`,
}
try {
const data = await axios.get('https://myurl.com/api/this-info', { headers });
return data;
} catch(error) {
console.error('error',error);
}
}
Related
I am creating a react app with full crud functionality. It allows users to create job postings and i wanted to click on a specific job to view more details.
I am having trouble as everytime i try to click a "job" it says that ID is undefined specifically:
show function called with id: undefined
SyntaxError: Unexpected end of JSON input
My app currently displays the list of all jobs and creates.
I already confirmed the following:
Made sure the backend server is running and listening on port 3001.
Verified that the endpoint i am trying to fetch actually exists. Tried on postman
Made sure that my frontend code is using the correct URL to make requests to the backend.
I am using hooks and functions.
DetailsPage.js
import styles from './DetailsPage.module.css';
import React, { useState, useEffect } from 'react';
import jobsService from '../../utils/jobsService';
export default function DetailPage(props) {
const [job, setJob] = useState({});
const [isLoading, setIsLoading] = useState(true);
const { id } = props.match?.params || {};
useEffect(() => {
const fetchData = async () => {
try {
const { data } = await jobsService.show(id);
setJob(data);
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
fetchData();
}, [id]);
return (
<>
{isLoading ? (
<div>Loading...</div>
) : (
<div className={styles.list}>
<div className={styles.Grid}>
<h3>{job.title}</h3>
<p>{job.description}</p>
</div>
</div>
)}
</>
);
}
jobsService.js
async function getAll(){
const response = await fetch('http://localhost:3001/api/jobs')
const data = await response.json()
return data
}
async function create(item) {
try {
const response = await fetch('http://localhost:3001/api/jobs/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
});
return await response.json();
} catch (error) {
console.error(error);
}
}
async function show(id) {
console.log("show function called with id:", id);
return fetch(`http://localhost:3001/api/jobs/${id}`, {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
})
.then(res => res.json());
}
export default {
getAll,
create,
show,
}
destruct id form empty object ?
instead this
const { id } = props.match?.params || {};
test this
const { id } = props.match?.params || {id: 0};
or use ternary operator in function calling
or
async function show(id = 0) {
console.log("show function called with id:", id);
return fetch(`http://localhost:3001/api/jobs/${id}`, {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
})
.then(res => res.json());
}
I make react app using react router v5, and axios as api instance. I fetch the data in AppRouter file.
Here is my approuter.tsx
const AppRouter = () => {
const dispatch = useAppDispatch();
const token = useAppSelector((state) => state.user.token);
const getUser = useCallback(async () => {
const { data } = await Services.getUser();
dispatch(userAction.setUser(data));
}, [dispatch]);
useEffect(() => {
const localStorage = new LocalStorageWorker();
const storageToken = localStorage.get('token');
dispatch(userAction.setToken(storageToken));
}, [dispatch]);
useEffect(() => {
if (token) {
getUser();
console.log('Worked');
}
}, [token, getUser]);
return (
...
)
}
Actually the function work properly, but I need to refresh the page manually to run these functions. How can I make the function run without refreshing the page?
Update:
The problem is because my axios create instance. I should use interceptors to keep the data fetching in useEffect.
My instance looks like this:
(before update)
const token = localStorage.get('token');
const createInstance = () => {
const instance = axios.create({
baseURL: BASE_URL,
headers: {
'content-type': 'application/json',
Accept: 'application/json',
},
});
instance.defaults.headers.common.Authorization = `Bearer ${token}`;
return instance;
};
(after update)
const createInstance = () => {
const instance = axios.create({
baseURL: BASE_URL,
headers: {
'content-type': 'application/json',
Accept: 'application/json',
},
});
instance.interceptors.request.use(
(config) => {
const token = window.localStorage.getItem('token');
if (token) {
return {
...config,
headers: { Authorization: `Bearer ${token}` },
};
}
return null;
},
(err) => Promise.reject(err)
);
return instance;
};
And now the data fetching is work properly. Thank you
So I architected frontend in the way which encapsulates every API operation tied to a single resource inside custom hook like this:
export default function useSubjects() {
const queryClient: QueryClient = useQueryClient();
const token: string | null = useStore((state) => state.user.token);
const { yearCourseId } = useParams<{ yearCourseId: string }>();
const getSubjects = async () => {
const response = await axios.get(`yearCourses/${yearCourseId}/subjects`, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
const postSubject = async (subject: SubjectType) => {
const response = await axios.post(`yearCourses/${yearCourseId}/subjects`, subject, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
const query = useQuery(SUBJECTS_QUERY_KEY, getSubjects);
const postMutation = useMutation(postSubject, {
onSuccess: (subject: SubjectType) => {
queryClient.setQueryData(SUBJECTS_QUERY_KEY, (old: any) => [...old, subject]);
},
});
return { query, postMutation };
}
Now what is the way to globally handle 401 unauthorized? I would like to navigate user to /login on every unauthorized request. Note that I have more hooks like this tied to other resources.
use the onError callback. You can also do this globally as a callback on the queryCache
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: error => {
// check for 401 and redirect here
}
})
})
I am try to fetch users information using github API
import React, { useEffect } from "react";
function UserResults() {
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
const response = await fetch(`${process.env.REACT_APP_GITHUB_URL}/users`, {
headers: {
Authorization: `token ${process.env.REACT_APP_GITHUB_TOKEN}`,
},
});
const data = response.json();
};
return <div>Hello</div>;
}
export default UserResults;
And here is what I put in my env:
REACT_APP_GITHUB_TOKEN="<token>"
REACT_APP_GITHUB_URL = "https://api.github.com"
I am sure the token is correctly generated and copied.
But it seems I can't fetch the data due to some "JSON" error as it shows in the console like this.
Can anyone offers any help with this?
You need to await response.json() and update your header request
import React, { useEffect } from "react";
function UserResults() {
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
const response = await fetch(`${process.env.REACT_APP_GITHUB_URL}/users`, {
headers: {
'Authorization': `token ${process.env.REACT_APP_GITHUB_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
});
const data = await response.json();
};
return <div>Hello</div>;
}
export default UserResults;
The token variable is not updating in SWR as I update it via useState in revalidate function.
const [token, setToken] = useState('')
console.log(token) // this updates as setToken is called
const fetcher = (url) => {
console.log(token) // this remains empty although it re-renders
return axios.get(
url,
{
headers: {
'Authorization': `Bearer ${token}`,
},
},
)
.then(res => res.data)
.catch(error => {//whatever})
}
const { data: user, error, revalidate } = useSWR('_ENDPOINT_', fetcher)
const login = (email, password) => {
axios.post('/login', {email, password})
.then((response) => {
setToken(response.data.token)
revalidate()
})
}
I end up using:
'Authorization': `Bearer ${localStorage.getItem('_token').
replace(/['"]+/g, '')}`,
'Authorization': `Bearer ${token}` // instead of this using from useState which didn't update in ages
Because it appears localStorage was available after useSWR init; (even on page refreshes)
You have to use useCallback on the fetcher function for it to properly pick up the token state variable change.
const fetcher = useCallback(
(url) => {
console.log(token) // Will log the updated `token` value
return axios
.get(
url,
{ headers: { 'Authorization': `Bearer ${token}` } }
)
.then(res => res.data)
.catch(error => {/*whatever*/})
},
[token]
)
A better solution, to avoid using useCallback, would be to move the fetcher function outside the component, and pass several arguments to the fetcher in the useSWR call. This has the benefit of using the token as the key for the request (in addition to the URL, making the caching more specific), and only making the request when token is defined.
const fetcher = (url, token) => {
return axios
.get(
url,
{ headers: { 'Authorization': `Bearer ${token}` } }
)
.then(res => res.data)
.catch(error => {/*whatever*/})
}
const SomeComponent = () => {
const [token, setToken] = useState('')
const { data: user, error, revalidate } = useSWR(token ? ['_ENDPOINT_', token] : null, fetcher)
// Rest of the component
}