NextJS useSWR XHR is succesful but variable undefined - reactjs

In the browser console I get a succesful request XHR to the api endpoint and the data is returned, however when I do a console.log(data1) the variable comes back undefined. What do I need to do to return this data to a nextjs app using useSWR react hook?
const FromDate = "02/01/2020";
const ToDate = "09/30/2020";
const baseURL = `http://example.com/test_db/system.sql?query=`
const fetcher = (url) => fetch(url, {
mode: 'no-cors',
headers: {
// 'Content-Type': 'application/json',
"Access-Control-Allow-Origin": "*"
}}
).then(res => res.json())
function updateDates (FromDate, ToDate) {
const url = `http://example.com/test_db/system.sql?query=sqlD`
const { data1, error } = useSWR(url, fetcher)
console.log("updatefunction", data1)
return {
data1,
isLoading: !error && !data1,
isError: error
}
}
const { data1, isLoading, isError } = updateDates("01/01/2018", "12/31/2019")
UPDATE* The error that I receive is:
SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data

Related

Not getting the localstorage token in my react component

I have token.ts which looks like this -
export const contentTypeHeaderWithToken = {
'headers': {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
};
In my reduxtoolkit, I am using this contentTypeHeaderWithToken like this -
export const fetchAllRoles = createAsyncThunk<IGetRolesResponse>(
'user/getroles',
async (user, {rejectWithValue}) => {
try {
const response = await Services.get('/roles', contentTypeHeaderWithToken);
return response.data.slice().reverse() as IGetRolesResponse;
} catch(err) {
const error = err as any;
return rejectWithValue(error?.response?.data)
}
},
);
In try statement above, contentTypeHeaderWithToken is coming out to be null for the very first time.
Before pulling the token value, I have verified, the token is present in the browser.
Any idea what I am missing ?

How to create a global 401 unauthroized handler using React + ReactQuery + Axios stack?

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
}
})
})

MOBX is concatenating an observable instead of updating it

I am building a login page with Mobx, MUI V5, react-router V6 and react-hook-form.
My first API call is to authenticate the application, apiAuth() will return a token that needs to be passed to all subsequent API calls.
On the next call, userAuth(), I try to validate the user credential.
As you can see, the method takes 3 arguments (a token, card number and, password)
When the user credentials are valid, I can login successfully.
When the user credentials are not valid on the first try, it works as
expected. I receive 400 (Bad Request) error from the API and display the error message on the
interface.
That said when I entered the user credentials once more, I get a 401 (Unauthorized) error.
Upon further inspection of the request headers, when I compared the authorization header in both userAuth() calls, I see that the token's value on the second call was concatenated with the previous token
Any ideas as to why for this behavior?
My AuthStore looks as follow:
class AuthStore {
isAuth = false
isAuthFail = false
AuthFailObj = {}
bearerToken = ''
cardNum = ''
password=''
constructor() {
makeObservable(this, {
isAuth: observable,
AuthFailObj: observable,
isAuthFail:observable,
bearerToken: observable,
cardNum: observable,
password: observable,
auth: action,
setIsAuth: action,
setToken: action,
setCardNum: action,
setPassword: action,
setIsAuthFail: action,
setAuthFailObj: action
})
}
setIsAuth = isAuth => {
this.isAuth = isAuth
}
setToken = bearerToken => {
this.bearerToken = bearerToken
}
setCardNum = cardNum => {
this.cardNum = cardNum
}
setPassword = password => {
this.password = password
}
setIsAuthFail = b => {
this.isAuthFail = b
}
setAuthFailObj = ojb => {
this.AuthFailObj = ojb
}
auth = async () => {
const apiRes = await apiAuth()
if (apiRes.status === 200){
const apiData = await apiRes.text()
this.setToken(JSON.parse(apiData)[0].token)
}
const userAuthRes = await userAuth(this.bearerToken, this.password, this.cardNum)
if (!userAuthRes.ok){
this.setIsAuthFail(true)
const errRes = await userAuthRes.text()
userAuthRes.status === 400 && this.setAuthFailObj(JSON.parse(errRes))
userAuthRes.status === 401 && this.setAuthFailObj('401 (Unauthorized)')
}
if (userAuthRes.ok){
const userAuthData = await userAuthRes.text()
userStore.updateUserProfile(JSON.parse(userAuthData))
this.setIsAuth(true)
}
}
}
export default new AuthStore()
In the login form, the submit method looks like this:
const submit = async (data) => {
AuthStore.setCardNum(data.Card_Number)
AuthStore.setPassword(data.Password)
setToggle(true)
await AuthStore.auth()
if (AuthStore.isAuth) {
navigate('/dashboard')
} else {
// clear form
}
}
Finally, the PrivateRoute logic reads is simple:
const PrivateRoute = () => {
return AuthStore.isAuth ? <Outlet /> : <Navigate to='/' />
}
The function userAuth()
const myHeaders = new window.Headers()
const { REACT_APP_API_ACC_MNG_AUTH_URL } = process.env
const userAuth = async (bearerToken, password, cardNum) => {
myHeaders.append('Authorization', `Bearer ${bearerToken}`)
myHeaders.append('Content-Type', 'application/json')
const raw = JSON.stringify({
cardNumber: cardNum,
pinNumber: password
})
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
const response = await window.fetch(REACT_APP_API_ACC_MNG_AUTH_URL, requestOptions)
return response
}
The issue is that you're using the Headers API and appending to the headers instead of setting them, which exist outside the function scope and are updated. From MDN:
The append() method of the Headers interface appends a new value onto an existing header inside a Headers object, or adds the header if it does not already exist.
So every time you make a request, if you append the header, it will be added on to the existing value. You could move your headers declaration inside of the function, and create a new object each time you make a request:
const { REACT_APP_API_ACC_MNG_AUTH_URL } = process.env
const userAuth = async (bearerToken, password, cardNum) => {
const myHeaders = new window.Headers()
myHeaders.append('Authorization', `Bearer ${bearerToken}`)
myHeaders.append('Content-Type', 'application/json')
const raw = JSON.stringify({
cardNumber: cardNum,
pinNumber: password
})
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
const response = await window.fetch(REACT_APP_API_ACC_MNG_AUTH_URL, requestOptions)
return response
}
Or you could just pass them in as an object, which is allowed by the Fetch API:
const userAuth = async (bearerToken, password, cardNum) => {
const raw = JSON.stringify({
cardNumber: cardNum,
pinNumber: password
});
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow',
headers: {
'Authorization': `Bearer ${bearerToken}`,
'Content-Type': 'application/json',
}
};
const response = await window.fetch(REACT_APP_API_ACC_MNG_AUTH_URL, requestOptions);
return response;
}

useSWR - How to pass config object to fetch

I'm trying to integrate useSWR in a next js project I'm working on.
I want to pass a config to fetcher as an argument. I have read about Multiple Arguments in the docs
but it's not returning the data for some reason. it is making the api request I can see that in the network tab.
not sure how to do this.
any suggestions?
const fetcher = async (url, config) => {
let res;
if (config) {
res = await fetch(url, config);
} else {
res = await fetch(url);
}
if (!res.ok) {
const error = new Error('An error occurred while fetching the data.');
error.info = await res.json();
error.status = res.status;
throw error;
}
return res.json();
};
const { data, error } = useSWR(
[
rolesUrl,
{
headers: {
Authorization: `Bearer ${user.token}`,
'Content-Type': 'application/json',
},
},
],
fetcher
);
After a very long debuging I found out. fetch is getting the config object.
and then makes the request to the api. then useSWR returns the response. which causes the component to re-render. the config object gets recreated.
useSWR thinks argument updated and make the api request again. that's why we don't get the data.
I have fixed this with useMemo hook
const config = useMemo(
() => ({
headers: {
Authorization: `Bearer ${user.token}`,
'Content-Type': 'application/json',
},
}),
[user.token]
);
const { data, error } = useSWR([rolesUrl, config], fetcher);

Handling errors within custom SWR hook

I've written a custom hook that uses SWR to retrieve data from my API whilst setting the 'Authentication' header for the request.
The hook is working fine for all successful requests but I want to be able to handle failed requests (400 status codes).
I'm able to access the status code with the result from const res = await fetch(url but how do I return the error in the error parameter to the caller of the hook?
import useSWR from 'swr';
export default function useAPI(path) {
const auth = useAuth();
const { data, error, isValidating, mutate } = useSWR(
!path ? null : `${process.env.NEXT_PUBLIC_API_URL}${path}`,
async (url) => {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${auth.user.token}`,
accept: 'application/json',
},
});
return res.json();
}
);
return { data, error, isValidating, mutate };
}
From SWR Error Handling documentation:
If an error is thrown inside fetcher, it will be returned as error by the hook.
In your case, you can simply handle to 400 status code response in the fetcher and throw an error after the handling is done.
const { data, error, isValidating, mutate } = useSWR(
!path ? null : `${process.env.NEXT_PUBLIC_API_URL}${path}`,
async (url) => {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${auth.user.token}`,
accept: 'application/json'
}
});
if (res.statusCode === 400) {
// Add your custom handling here
throw new Error('A 400 error occurred while fetching the data.'); // Throw the error
}
return res.json();
}
);

Resources