How to programmatically (and idiomatically) navigate to an external URL? - reactjs

I am working on a React microservice. At some point we verify the user data to have a certain value, otherwise we want to redirect to an external URL. That sounds easy, but I am not sure if my current solution is idiomatically correct.
React router doesn'T seem to provide anything beyond the current domain. I can only change the path, using the history object. Is there something I overlooked?
I am using window.location.href now to get the current URL, as I need to pass this as a callback, and window.location=xyz to navigate. but it feels wrong.
// ComponentX.js
...
if (name === null) {
window.location = selectOrgURL(window.location.href);
}
render (
...

Related

Can I detect if using Next.js router.back() will leave my site and do a router.push('/') instead?

I currently have a "go back" link on a page that currently uses router.back() in the onClick handler to return to the previous page, which normally works if the current page was navigated to within my site, but if the page was navigated to directly (say, via a bookmark or a pasted URL) then I would like the "go back" link to do a router.push('/') to go back to my home page instead. I don't know how to determine if the previous browser history URL is outside my site to do the router.push() instead of the router.back(). Anyone have any suggestions?
You can use window?.history?.state?.idx to check the index of the state representing the latest visited page.
If window?.history?.state?.idx > 0, that means that the user's been navigating on the website and you can "safely" use router.back(...).
If the url is directly pasted in the browser, this value will be equal to 0.
You can check out document.referrer and check that the URL's hostname matches yours. If it does, you can safely use router.back(). If it doesn't, you can use router.push('/').
Unfortunately, document.referrer doesn't work with Next correctly.
The preferred method would be utilising a session storage to save previous and current paths using useEffect hook in _app.js
...
const storePathValues = () => {
const storage = globalThis?.sessionStorage;
if (!storage) return;
// Set the previous path as the value of the current path.
const prevPath = storage.getItem("currentPath");
storage.setItem("prevPath", prevPath);
// Set the current path value by looking at the browser's location object.
storage.setItem("currentPath", globalThis.location.pathname);
}
useEffect(() => storePathValues, [router.asPath]);
...
With this, you can evaluate from which page the user came and then utilize either router.back() or router.push().

Intercepting and manipulating browser history with react router history

I'm trying to intercept history call in react app and manipulate it to change search parameter of url when it tries to route to certain path. I'm successfully on listening but changing location parameters or replacing history (I want to add ?default_param=value) is not working. It still change location using old parameters. My example code:
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
history.listen(() => {
if (
history.location.pathname === '/path-to-manipulate' &&
!history.location.search
) {
const unblock = history.block();
// this one doesn't change current redirection
history.push({
pathname: '/path-to-manipulate',
search: '?default_param=value',
});
/* this one is also not working
history.replace({
pathname: '/path-to-manipulate',
search: '?default_param=value',
});
*/
unblock();
}
});
Long time ago there was history.listenBefore() https://github.com/ReactTraining/history/issues/379, but it was removed from api and now we have history.block(). I tried with and without block, with different combination of replace/push/forward and event changing history.location explicite. Nothing of that would allow me to properly intercept location before change, alter it and execute with new parameter ?default_param=value.
How to properly intercept location change and change it (location params) before it executes and redirect?
Two more things:
I don't want to mess with <Link to=...>
components and implement logic there. I wanted something global that could manage routing on higher level in react app.
listen() won't work when entering url in browser, it only works when using routing in app. Is there another way to catch first reference on certain location in react app?
What I usually do is try to read the default_param value, and if there is nothing I return with <Redirect to... />
"I wanted something global that could manage routing on higher level in react app."
Why is it? You want a global solution for a problem which is very specific?
If this problem happens a lot (you need a default param), you can write a HOC which takes a parameter and a default value and either return with a <Redirect /> or the wrapped component depends on you have the parameter in the url

React router -- How to send props without query params or Redux?

I want to send data to another route, but don't want to send it in query params.
I don't want a new store for every route, nor do I want a store that simply holds all routes / params separately from where they are sent / consumed.
Is there a standard way to specify props for an upcoming route?
I found the solution on the react-router location api docs.
this.props.router.push({
pathname: '/view-user',
state: { userId }
});
This seems great for interstitial, standalone modal pages.
May need to specify a fallback if the state is missing, but haven't quite gotten that far.
if (!this.props.location.state) this.props.router.goBack();
or
const locations = this.props.location.pathname.split('/');
// do something
this.props.route.push(locations.join('/'));
If you are not sending the information in the query param, then you can put it in some other kind of store that can also be associated with the route.
You can wrap the router.push() call with your own function that takes an extra object you want to pass along. Something like...
function navigateTo(url, extraData) {
if (extraData !== undefined) {
saveExtraDataForRoute(url, extraData);
}
router.push(url);
}
In react-router, there is an onEnter prop associated with the route that specifies a function to call. The code in this function can retrieve the extra data and do whatever you want to do with it.
function onMyScreenEnter() {
const extraData = getExtraDataForRoute(url);
goCrazyNutsWithYourExtraData(extraData);
}
You'd supply the two functions saveExtraDataForRoute() and getExtraDataForRoute(). They could use your store (e.g. Redux), set values of a singleton object, or use LocalStorage. But essentially, to save the data so it's retrievable by URL later, you'd be saying something like:
extraDataStore[url] = extraData;
The other thing you may wish to look into is using a POST method with react-router. I haven't done this, and am not sure how well it works. Here is a link: How to Handle Post Request in Isomorphic React + React Router Application

React-redux get site base URL / window.location

Hopefully this is a very simple question:
I want to create a string containing the full URL to a page on my site, like:
https://example.com/documents/1
Ideally I'd like to generate this in a react-redux connect()-ed container, in mapStateToProps(). Where the page is a grandchild of a react-router Route using browserHistory (so maybe I can get it from react-router or browserHistory somehow?) If necessary I'd do it in a full-on React class.
But I can't for the life of me find out how to do this. window (as in window.location) is always undefined in mapStateToProps() (no big surprise there) and in my view components' render() function.
So far I have only found ways to access the current route, e.g. "/documents/1", which is only a relative path and does not contain the site base url (https://example.com/)
So firstly, remember your code runs on client and server so any access to window needs to be wrapped in a check.
if (typeof window !== 'undefined') {
var path = location.protocol + '//' + location.host + '/someting'; // (or whatever)
} else {
// work out what you want to do server-side...
}
If the URL you generate shows in the DOM, and on the client you populate the store with this URL before the first render, you're going to get a checksum error. If you really want to avoid the error you can wait until componentDidMount() in your root component to get/set the url.
After componentDidMount you can have direct access to the origin location so that you can get the root url.
I use the below code in my JSX all the time so that i dont have to worry about the root path ex: dev env this would be localhost:3000/whatever, staging this will be AppStaging.heroku/whatever/..... and in production it would evaluate to www.myapp.com/whatever
`${window.location.origin.toString()}/whateverRoute/`
You Can try!!
window.location.origin
you can try the URL API. Take a look at this link for more details https://developer.mozilla.org/en-US/docs/Web/API/URL
const url = new URL("blob:https://mozilla.org:443/")
console.log(url.origin);
// Logs 'https://mozilla.org'

Accessing react-router from flummox action/store

I want to be able to make an API call in a Flummox action and transition differently depending on the response. I can pass the router into the action call but am looking for advice on a potentially better way.
UPDATE:
The correct answer is below but I wanted to add some detail to this.
I'm doing an isomorphic app that 1. needs to get data from an api and 2. may need to redirect based on the api response. Whatever I do needs to work through an express.js app and through react.
I made a small lib that does the api call and return some results. I pass it an object (query params object from express for the server-side or a similar object I create for the react-side). This lib makes the request, determines if a redirect is needed and passes back errors, path (string), redirect (boolean), and data (json).
In express, if the redirect boolean is true, I just redirect to it with the current query params. If it's false, I pass the data to flux through an action which updates a store. I then renderToString with react, serialize stores so the clint-side can bootstrap, and send a rendered page to the client.
In react, the redirect boolean isn't important, I get the response back from my lib, pass the data to my flux action, and just transition to whatever the path is. There's really no notion of redirection. Just go to the path no matter what.
Hopefully this is helpful to someone.
In my setup I have my own router module which just wraps the instance of react-router that I create at startup. That makes it easy for any part of the application to just require that module and do what it needs to.
But I would advise you not to have side effects like a call to the router inside your actions. Actions should concern themselves on mutating your application state, and nothing more. It should be possible to call the same action from anywhere in your application which needs to perform the mutation that the action encapsulates.
So if you're switching routes during an action, you're basically tying that action to that particular use case. Let's take an example. You have a todo list, with an input box at the bottom to add a new todo. For that use case, it might be useful to switch route after you saved the todo. Perhaps you switch to Recent Todos or something. But then a new use case comes along where you want to be able to add new todos during another workflow, perhaps the user is planning her week and should be able to add todos on different days. You want the same action that adds a todo, but you certainly don't want to switch routes because the user is still planning the week.
I haven't used Flummox a lot, but from my understanding your Flux object returns whatever the action returns when you trigger an action. So instead of putting the route change inside your action, make sure to return the response from the action and let your component decide if the route should be changed. Something like this:
// todo-action.js
class TodoActions extends Actions {
createMessage(todo) {
return TodoStore.saveToServer(todo);
}
}
// todo-list.js
const TodoList extends React.Component {
render() {
...
}
addTodo(todo) {
this.props.flux.addTodo(todo).then(response => {
if (response.some.prop === someValue) {
this.props.router.transitionTo(...);
}
});
}
}
That way, the action is still nicely decoupled from the route change. If you want to do the route switch in more than one place, you could encapsulate that in a addTodoAndSwitchRoute method in your Flux class.

Resources