How to access react-router v3 prop in function? - reactjs

I'm using refresh-fetch for authentication token refreshing. If the app receives not 200 http status code response I need to handle that by redirecting the user to logout page. How could I achieve this using react-router v3.
browserHistory.push('/logout');
I think this is not an option because I'm using basename.
const refreshToken = () => {
return fetchJSONWithToken(`${API_ROOT}user/login/refresh`, {
method: 'POST',
body: JSON.stringify({ refresh_token: retrieveToken() })
})
.then(({body}) => {
saveToken(body.access_token, body.refresh_token);
return body;
})
.catch(error => {
//TODO: redirect user to /logout
throw error;
});
};
Or maybe there is a better way of doing this?

You need to store your browserHistory instance and reuse it.
Example:
import { createHistory, useBasename } from 'history'
// Run our app under the /base URL.
const yourCustomHistoryWithBasename = useBasename(createHistory)({
basename: '/base'
})
// Re-use the same history, which includes the basename
yourCustomHistoryWithBasename.push('/logout') // push /base/logout
yourCustomHistoryWithBasename.replace('/logout') // replace current history entry with /base/logout
Source for this example

Related

Redirecting from exported handle hook in sveltekit

I have a sveltekit app and I want to check if the user has an accesstoken from the cookie. I can access it through event.request.headers.get('cookie'); and redirects them to a certain route but I can't find specific syntax from the sveltekit docs.
src/hooks.ts
export async function handle({ event, resolve }) {
const reqCookie = event.request.headers.get('cookie');
const cookieName = 'userid';
const keeperCookie = reqCookie.split(';')
.find((c: string) => c.trim().startsWith(cookieName));
const response = await resolve(event);
if (!reqCookie || !keeperCookie) {
return response.headers.set('location', '/create');
}
return response.headers.set('location', '/login');
}
Redirect doesn't work for me and gives me an error in the console
I just got it using native Response
`return Response.redirect(baseUrl+"/login", 303);`
return new Response('Redirect', {status: 303, headers: { Location: '/login' }});
So you don't need the base url :)

Communication between React Context and Apollo Client Link

I am working on a single page webapp using React and Apollo Client, and I am wondering about how to correctly communicate authentication state between Apollo Client links and React context.
Within the client, I have written an AuthProvider context that supplies the current user information, so that anywhere in the component tree I can do
const authState = useAuthState()
const dispatch = useAuthDispatch()
and thus query and update the authentication information as I need. I have used this, for example, to write a PrivateRoute component that redirects the viewer if she is not authenticated:
const PrivateRoute: FunctionComponent<RouteProps> = ({ children, ...rest }) => {
const authState = useAuthState()
return (
<Route
{...rest}
render={({ location }) =>
authState.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
)
}
This is all fine. My issue arises when combining this with my chosen form of authentication, which is JWT. I am storing the access token in the authState and the refresh token is set as an httpOnly cookie by the back-end on login.
But I have to send the access token as an Authorization: Bearer header on each request, which I want to do using an Apollo Link, as follows:
const authLink = setContext(async (_, { headers }) => {
const token = getTokenFromAuthStateSomehow()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ""
}
}
})
But this is within an Apollo Link, where I of course don't have direct access to the React Context. getTokenFromAuthStateSomehow() is a function I do not know how to write.
Then the next issue is what happens when this request fails because the access token has expired. I want to use Apollo's onError to catch a 401 error from the API and retry the request by getting a refreshed token:
const retryLink = onError(({ networkError }) => {
if (networkError) {
const newToken = getRefreshedToken()
if (newToken) {
retryRequest()
setTokenInAuthStateSomehow(newToken)
}
}
})
But then we have the same problem - now I need to send the new token back to authState, i.e. to React Context: setTokenInAuthStateSomehow() is a function I do not know how to write either.
So, the overarching question is: how do I communicate between an Apollo Link and React Context? Do I have to setup some listeners or events somehow? I would love any information or push in the right direction.

URL gets changed after doing history(dot)push but history(dot)location(dot)pathname remains same in react

Navigation is working perfectly when I click. But after registering a user, when I do history.push('/login') then it changes the URL in the URL bar and goes to the login page, but if I console.log(history.location.pathname) then still the pathname remains to /register and active link is highlighted in register nav link even after going to the login page. How to fix this.
Even I tried passing history from react-router using
const history = createBrowserHistory()
<Route render={(props)=><Navbar {...props} history={history}/>}/>
It didn't work. So, how to fix it?
export const registerUser = (fields, history) => async dispatch => {
const config = {
headers:{
'Content-Type': 'application/json'
}
}
const body = JSON.stringify(fields)
try{
dispatch(createAction(LOADING_TRUE))
const response = await axios.post(`${api}/signup`, body, config)
dispatch(createAction(USER_SUCCESS, response.data))
dispatch(setGlobalMessage('positive', 'User registered successfully'))
dispatch(createAction(LOADING_FALSE))
history.push('/login')
}catch(error){
dispatch(createAction(LOADING_FALSE))
console.log(error.response);
error.response.data.errors
? dispatch(createAction(USER_FAILURE, error.response.data))
: dispatch(setGlobalMessage('negative', error.response.data.msg))
}
}
Before you route to the page, you can clear history by
window.history.replaceState(null, '')
more info here

After submitting my React form, is there an easier way of passing an object to the page I want to redirect to?

I'm using React 16.13.0. I have the following handler for submitting my React form to its endpoint ...
const handleFormSubmit = (e) => {
...
fetch(REACT_APP_PROXY + "/coops/", {
method: "POST",
body: JSON.stringify(NC),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw response;
}
})
.then((data) => {
const result = data;
window.location.href = "/" + result.id + "/people";
}).catch(err => {
err.text().then( errorMessage => {
setErrors({ errors: errorMessage });
});
});
};
I was wondering if there is a more "React" way of redirecting to the next page. Right now, I'm doing
window.location.href = "/" + result.id + "/people";
which seems kind of hacky. Also, I'm not able to pass an object to the next page, which I have on the page executing the submit (The "data" object is what ideally I'd like to pass to the next page). Thus, when the redirect page loads, I'm forced to execute another fetch call to retrieve that object again.
React, Passing objects between page loads / location changes:
you can use the localStorage/sessionStorage as described in the accepted answer here.
or you use React Context or Redux to store the object in global state that is shared across the application
React, Routing & Passing objects:
First of all, nothing is wrong with updating window.location. It is a valid way to update the current page. Leaving it as is and using sessionStorage to pass your object will most likely be the fastest way to solve your problem. That being said, there are me more optimized solutions out there that feel more like react: Try React Router for instance. It also lets you pass objects between routes by passing them down as props to the other pages.
react-router-dom hooks example:
import { useHistory } from "react-router-dom";
const myComponent = () => {
const history = useHistory();
...
.then((data) => {
const result = data;
// access state via this.props.location.state.result
history.push({
pathname: "/" + result.id + "/people",
state: {result}
});
}).catch(err => {
err.text().then( errorMessage => {
setErrors({ errors: errorMessage });
});
});
}
Find more information about react-router-dom here.

React Relay Modern redirecting to another page when receiving 401 error on network environment

I´m using JWT authentication inside my ReactJS RelayJS network environment. All the token retrieval and processing in server and client are fine. I´m using react router v4 for routing.
My problem is when I receive a Unauthorized message from server (status code 401). This happens if the user points to an application page after the token has expired, ie. What I need to do is to redirect to login page. This is the code I wish I could have:
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const SERVER = 'http://localhost:3000/graphql';
const source = new RecordSource();
const store = new Store(source);
function fetchQuery(operation, variables, cacheConfig, uploadables) {
const token = localStorage.getItem('jwtToken');
return fetch(SERVER, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
Accept: 'application/json',
'Content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables
})
})
.then(response => {
// If not authorized, then move to default route
if (response.status === 401)
this.props.history.push('/login') <<=== THIS IS NOT POSSIBLE AS THERE IS NO this.history.push CONTEXT AT THIS POINT
else return response.json();
})
.catch(error => {
throw new Error(
'(environment): Error while fetching server data. Error: ' + error
);
});
}
const network = Network.create(fetchQuery);
const handlerProvider = null;
const environment = new Environment({
handlerProvider, // Can omit.
network,
store
});
export default environment;
Naturally calling this.props.history.push is not possible as the network environment is not a ReactJS component and therefore has no properties associated.
I´ve tried to throw an error at this point, like:
if (response.status === 401)
throw new Error('Unauthorized');
but I saw the error on the browser console, and this cannot be treated properly in the code.
All I wanna do is to redirect to login page in case of 401 error received, but I can´t find a proper way of doing it.
I am not using relay but a render prop. I experienced kind of the same issue. I was able to solve it using the window object.
if (response.statusText === "Unauthorized") {
window.location = `${window.location.protocol}//${window.location.host}/login`;
} else {
return response.json();
}
You can go with useEnvironment custom hook.
export const useEnvironment = () => {
const history = useHistory(); // <--- Any hook using context works here
const fetchQuery = (operation, variables) => {
return fetch(".../graphql", {...})
.then(response => {
//...
// history.push('/login');
//...
})
.catch(...);
};
return new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource())
});
};
// ... later in the code
const environment = useEnvironment();
Or you can create HOC or render-prop component if you are using class-components.
btw: this way you can also avoid usage of the localStorage which is slowing down performance.

Resources