I am following this video tutorial from Rem Zolotykh. I am having the problem that querying the LoopBack server within an onSubmit() from a Form works and using a Redux Action with the same query gives me a Cross-Origin error.
LoopBack Server running at localhost:3000
React Webpack Server
running at localhost:3001 (I used create-react-app)
This following onSubmit function works. Please don't mind the hardcoded stuff it is just for testing.
--------------SignupForm.js-----------------
...
onSubmit(e) {
const user_data = { "email": "foo#bar.com",
"password": "xxx" };
axios.post('http://localhost:3000/api/Users/login', user_data)
.then((response) => {
auth_token = { headers: { 'Authorization': response.data.id } };
return axios.get('http://localhost:3000/api/empsecure', auth_token)
})
.then((response) => {
console.log('Queried Data:', response);
return axios.post('http://localhost:3000/api/Users/logout',{},auth_token)
})
.then((response) => {
console.log("logged out", response);
});
}
...
Here is the changed onSubmit() and the Redux Action:
--------------SignupForm.js-----------------
...
onSubmit(e) {
this.props.userSignupRequest(this.state);
}
...
-------------signupActions.js---------------
import axios from 'axios';
export function userSignupRequest(userData) {
return dispatch => {
const auth_token = {
headers: {'Authorization': 'BgKeyYGVWxx5ybl649jhiPiHpZAyACmV6d9hfJ5UAJxs1McR4RaqANBgwP8vEqiH'}
};
axios.get('http://localhost:3000/api/empsecure', auth_token)
.then((response) => {
console.log('Queried Data:', response);
return response
});
}
}
The browser console gives me a Cross-Origin error, I understand that. But why does it work without redux then?
Ok, after researching, surfing internet and lot of code changes, I found its required in this case to prevent the default action for onSubmit().
I think it did not like the page reload. Works now.
onSubmit(e) {
e.preventDefault();
this.props.userSignupRequest(this.state);
}
Related
I am using Remix, along with Remix-Auth and using the Twitch API/OAuth, which requires that I check in with their /validate endpoint every hour docs. I had someone recommend that I use a resource route and POST to that if the validation endpoint returned a status of 401, however, I need as I stated before the request needs to be sent every hour I figured maybe I could use something like React-Query to POST to the resource route every hour.
Just pointing out that I use createCookieSessionStorage with Remix Auth to create the session
Problem
I haven't been able to achieve the actual session being destroyed and a user being re-routed to the login page, I have left what actual code I have currently any help or suggestions to actually achieve the session being destroyed and be re-routed to the login page if the validation fails would be greatly appreciated.
// React Query client side, checks if the users token is still valid
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
The above React Query returns this
// My attempt at the resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
})
}
// Second attempt at resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return destroySession(session)
}
I attempted using an if statement to POST to the resource route or else render the page, however, this definitely won't work as React errors out because functions aren't valid as a child and page is blank.
//index.tsx
export default function Index() {
const { user, bits, vali } = useLoaderData();
console.log("loader", vali);
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
if (data?.status === 401)
return async () => {
await fetch("~/services/destroy.server", { method: "POST" });
};
else
return ( ... );}
You could use Remix' useFetcher hook.
https://remix.run/docs/en/v1/api/remix#usefetcher
// Resource route
// routes/api/validate
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request);
try {
const { data } = await fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${session.get("token")}`
}
});
return json({
data
}, {
headers: {
"Set-Cookie": await commitSession(session),
}
});
} catch(error) {
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
});
}
}
And then in your route component something like this:
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.type === 'init') {
fetcher.load('/api/validate');
}
}, [fetcher]);
useEffect(() => {
if(fetcher.data?.someValue {
const timeout = setTimeout(() => fetcher.load('/api/validate'), 1 * 60 * 60 * 1000);
return () => clearTimeout(timeout);
}
},[fetcher.data]);
I am trying to implement an React solution with Strapi as backend where authorization is done using JWT-keys. My login form is implemented using the function below:
const handleLogin = async (e) => {
let responsekey = null
e.preventDefault();
const data = {
identifier: LoginState.username,
password: LoginState.password
}
await http.post(`auth/local`, data).then((response) => {
setAuth({
userid: response.data.user.id,
loggedin: true
})
responsekey = response.data.jwt
setLoginState({...LoginState, success: true});
sessionStorage.setItem('product-authkey', responsekey);
navigate('/profile');
}).catch(function(error) {
let result = ErrorHandlerAPI(error);
setLoginState({...LoginState, errormessage: result, erroroccurred: true});
});
}
The API-handler should return an Axios item which can be used to query the API. That function is also shown below. If no API-key is present it should return an Axios object without one as for some functionality in the site no JWT-key is necessary.
const GetAPI = () => {
let result = null
console.log(sessionStorage.getItem("product-authkey"))
if (sessionStorage.getItem("product-authkey") === null) {
result = axios.create(
{
baseURL: localurl,
headers: {
'Content-type': 'application/json'
}
}
)
} else {
result = axios.create({
baseURL: localurl,
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${sessionStorage.getItem("product-authkey")}`
}
})
}
return result
}
export default GetAPI()
However, once the user is redirected to the profile page (on which an API-call is made which needs an JWT-key), the request fails as there is no key present in the sessionStorage. The console.log also shows 'null'. If I look at the DevTools I do see that the key is there... And if I refresh the profile page the request goes through with the key, so the key and backend are working as they should.
I tried making the GetAPI function to be synchronous and to move the navigate command out of the await part in the handleLogin function, but that didn't help.
Does someone have an idea?
Thanks!
Sincerely,
Jelle
UPDATE:
Seems to work now, but I need to introduce the getAPI in the useEffect hook, I am not sure if that is a good pattern. This is the code of the profile page:
useEffect(() => {
let testapi = GetAPI()
const getMatches = async () => {
const response = await testapi.get(`/profile/${auth.userid}`)
const rawdata = response.data.data
... etc
}, [setMatchState]
export default GetAPI() this is the problematic line. You are running the GetApi function when the module loads. Basically you only get the token when you visit the site and the js files are loaded. Then you keep working with null. When you reload the page it can load the token from the session storage.
The solution is to export the function and call it when you need to make an api call.
Using React 16.12.0. We have a number of fetch calls that resemble
const handleFormSubmit = (e) => {
e.preventDefault()
if (password != passConfirm) {
//handle password doesn't match password confirm on submit
setErrors({passConfirm: ["Must match password"]})
return
}
fetch(REACT_APP_PROXY + '/users/', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'Authorization': `Token ${sessionStorage.getItem('token')}`},
body: JSON.stringify({first_name, last_name, username, password, email})
}).then((response) => {
if (response.ok) {
sessionStorage.setItem('token', response['Refresh-Token'])
setRedirect(true)
} else {
setErrors(response)
console.log(response)
}
})
Note the section where we have an ".ok" response
sessionStorage.setItem('token', response['Refresh-Token'])
We will have a number of these endpoints where we will want to extract this response header and place it in local storage. Is there a more elegant way of applying a response filter to certain endpoints to implement this behavior as opposed to the way we are doing it now?
I don't think there is a possibility for this using fetch except monkey patching fetch itself:
const { fetch: originalFetch } = window;
window.fetch = async (...args) => {
let [url, options] = args;
// you can run any request logic here
const response = await originalFetch(url, options);
// you can run any response logic here
return response;
};
But, there must be someone that already did this, so I stumbled upon this npm library fetch-intercept that you can use to do the similar:
import fetchIntercept from 'fetch-intercept';
const unregister = fetchIntercept.register({
response: function (response) {
// Do something with the response
if (response.ok) {
sessionStorage.setItem('token', response['Refresh-Token'])
}
return response;
},
});
// you can call unregister if you don't want run the interceptor anymore
unregister();
Please be aware of the following: You need to require fetch-intercept before you use fetch the first time.
If you are willing to switch from fetch to axios, you can use interceptors and handle it globally.
import axios from 'axios'
axios.interceptors.response.use(response => {
if (response.headers['Refresh-Token']) {
sessionStorage.setItem('token', response.headers['Refresh-Token'])
}
return response
})
Reference: https://axios-http.com/docs/interceptors
Use a function.
async function makeHttpCall(successCallbk) {
var response = await fetch(...arguments);
if(response.ok) {
sessionStorage.setItem('token', response['Refresh-Token']);
successCallback.call();
} else {
setErrors(response);
console.log(response);
}
}
I am working on a React JS project. In my project, I am using React query, https://react-query.tanstack.com/docs/guides/mutations. I am using mutation to make the post request to the server. But I am trying the get the response returns from the server when the API call fails with the onError call back.
This is my code.
let [ createItem ] = useMutation(payload => createItem(payload), {
onSuccess: (response) => {
},
onError: (error) => {
// here I am trying to get the response. In axios, we can do something like error.data.server_error_code
},
onMutate: () => {
}
})
As you can see in the comment, I am trying to read a field returned from the server within the onError callback. How can I do that?
let [ createItem ] = useMutation(payload => createItem(payload), {
onSuccess: (response) => {
},
onError: (error) => {
console.log(error.response.data);
console.log(error.response.status);
},
onMutate: () => {
}
})
It's not entirely clear when just doing console.log(error) inside onError, but error.response should be available.
It should work as it is. Make sure that your HTTP client (probably, Axios) is configured to throw an error. For example:
import axios from 'axios'
import { useMutation } from 'react-query'
import { BASE_URL } from 'constants/api'
const client = axios.create({
baseURL: BASE_URL,
})
const request = (options) => {
const onSuccess = (response) => response
const onError = (error) => {
// Throwing an error here
throw error
}
return client(options).then(onSuccess).catch(onError)
}
const { mutate } = useMutation(
async (data) =>
await request({
url: '/someUrl',
method: 'post',
data
}),
{ onError: (e) => console.log(e) }
)
And of course, it's better to store your Axios settings within a separate file, and then just import the 'request' variable where mutations are using.
If you are using fetch, you have to know that fetch does not throw any error unless is a network problem (as read here)
My solution was just to change to axios (which throws error when 400 or 500), but if you still need to use fetch, you need to find a way to make it throw errors instead.
I think the issue with NOT having an error.response in the callback depends on how the API is failing. If you look at the react-query documentation it shows that most HTTP libs like axios will throw if there is a non 2xx response. However it's up to the underlying API function how it handles that.
For example axios https://axios-http.com/docs/handling_errors will return the response object if there is a response from the server. They will return the request if the call has timed out and return just a message if the previous two don't fit the error
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
However, if you're using the Fetch API you have handle this yourself. Taken straight from react-query's docs: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default
useQuery(['todos', todoId], async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
})
I have pasted the code which is used for log in my application. Even though the login is successful, the page is not redirecting When login on the first time. After that its working properly. On the first time, the response is goes to catch block also even the response is 200. How to solve this issue.
export function login(loginForm){
return function(dispatch) {
axios.post(config.api.url + 'public/signin', { "email": loginForm.email, "password" : loginForm.password})
.then(response => {
dispatch({ type: 'AUTH_USER' });
localStorage.setItem('token', response.data.token);
hashHistory.push('/');
location.reload();
})
.catch(response => {
dispatch(authError(response.response.data.error));
});
}
}
Try using this -
static contextTypes = {
router: React.PropTypes.object
};
this.context.router.push('/');