How do you pass data from one view to the other via react-router-dom without using url parameters? - reactjs

I use react-router-dom v 4.3.1 for client-side routing. I'm pretty new to React and can't figure out how to pass data from one view to the other without using url parameters. In Angular, the Angular router has a data property where you can pass data associated with a route. An example would be:
const appRoutes: Routes = [
{
path: 'hero/:id',
component: HeroDetailComponent,
data: { title: 'Hero Detail' }
},
];
Can you do the same in react-router-dom? If not, how would you recommend I pass data in React?
Thanks in advance for the help!

<Route path="hero/:id" render={() => <HeroDetailComponent title= "Hero Detail" />} />
Read this: Pass props to a component rendered by React Router
Or if you are using <Link> you can use pass through location object
<Link to={{ pathname: 'hero/:id', state: { title: 'Hero Detail'} }}>My route</Link>

Well you Could use the context API to create a sort of global AppState that you could update in your first component and use in your second component.
You could also abuse the localStorage API by setting a key with the data in the first component and getting it in the other.
However both of these are workarounds that Shouldn't have to be used. Why do you want to redirect to a page but not pass data to it using URL parameters.

There'a several solutions. React being a library, not a framework, doesn’t force you into a single one.
One way is to use the context api. It’s like a link to an object shared between different components.
Another one is redux, which uses context underneath, and gives you a single store for the whole app. You changes values dispatching actions to the store, so it’s a bit tricky to learn the first time.
Using a stream library would open up a lot of different options, but it’s harder to get into. Check refract if you want to go this way.
A poor man’s stream approach that may serve you is using document as a bus to pass data arround, using addEventListeners to receive data and dispatch new customEvent to send it.
Next is the simplest one of all, share a simple object. Using imports form your components, you can import the same object on both and that will be a single instance where data can be shared. Simple JavaScript. It’s not the react way though, because changes won’t trigger a repaint on the component.

Related

Is there any option to change route and pass props to another component in Next.js?

I am at this path: http://localhost:3034/dashboard/one.
Now I want to pass a large object in another component <Edit/> that has an object and also change on the path to http://localhost:3034/dashboard/edit.
I don't want to use the next router to pass all objects with queries in URL. That one way, I know.
Example: router.push("/dashboard/edit/" + object)
I have data that pass from this slug: http://localhost:3034/dashboard/one to http://localhost:3034/dashboard/edit.
If you don't want to use the router to pass your data, a few options are left.
using a Reducer: handy and powerful, but lots of boilerplate to get it working
using a Context: powerful as well, a little tricky to master at first, and can trigger lots of redraws if not used properly
using the local storage: very easy to setup, but requires your data to be serializable, You can use libraries such as react-use to make your life easier
refetch your item in your edit page using an id: you could have your url like /dashboard/one/edit/ and fetch the data directly from that page instead of carrying the object around

Pass state via Link while redirected to a new tab

case 1:
Redirected to a new route in the same tab.
<Link to={{ pathname: "/modify_items", state: { foo: "bar" } }} >
When I type console.log(this.props.location.state); the state { foo: "bar" } get printed.
case 2:
Redirected to a new route in a new tab.
<Link to={{ pathname: "/modify_items", state: { foo: "bar" } }} target="_blank" >
When I type console.log(this.props.location.state); the state becomes undefined.
My goal is to pass data to a new tab. Please guide me if there is any other way to achieve this in React.js. I just want to pass data to a new tab.
I think this is not possible with Link state. Cause if you leave the page your memory will be clean. You might want to use query or path params if you want to share state over different tabs.
For path params:
Get path params in react-router v4
For query variables:
Set the Link as followed:
<Link to={{ pathname: '/foo', query: { foo: "bar" } }}/>
and use maybe a library to get the query param. Such as https://www.npmjs.com/package/query-string.
Since the _blank opens a new tab so it is another instance of your app even the app is the same app the internals are different. But you can use something like localStorage.
Since they will be persisted and will be shared for same origin you will have access to it in the new or any tab or window of your application. You need to query it in the component mount or after first render with and useEffect hook with a second parameter being probably empty array, []. For hooks checkout React docs if you use them.
Another way is to use the querystring. You can receive the query string from your opened comopenent instance.
I am sure you can use any other persistense mechanism, like Indexed DB, Cookies, etc.
There are a few other ways coming into my mind under the umbrella web messaging; Broadcast Channel API, Window.postMessage, and Channel Messaging API that you may make use of.
If you state is relatively small it mifght be good to use querystring, or localStorage is the easiest way to go. If your data way more bigger you can use Indexed DB. It is async. Only be careful when it completes write, then you get it in your new window, tab.
In persistence usage cases, write to persistence before you navigate, just wait the write process if it is async before you navigate (by opening new tab/window in your specific use case).
One might navigate to a new tab maybe by right clicking and selecting from the context menu. ın that case Page Visibility API can be used on the right clicked page in a React effect (attaching its effects in an React effect with an empty second argument to React effect) and the data can be written again to a persistence mechanism, one of above mentioned ones, and the rest I mean the operations in the opening tab will be the same.
Since the writing mechanism can be late and even it is a sync one opening of a new tab if it is not programmatical might happen before the writing is done. In that case the opening tab's React effect to read the data written might be early to read. To circumvent these workarounds like waiting via a setTimeout to read can be used simply, however, a programmatically correct and a guaranteedly timed way would be using one of the appropriate web messaging apis I have mentioned above like Broadcast Channel, postMessage, and Channel Messaging API.
It is not possible to make it via React Router.
But you may use localStorage to save something there before you redirect to new tab and then check if something exists in localStorage on new tab.
It's working for me:
<Link to="/onboarding/profile" state={{ from: "occupation" }}>
Next Step
</Link>
next step:
import { useLocation } from 'react-router-dom'
function Profile () {
const location = useLocation()
const { from } = location.state
return (
...
)
}

Pass data to react routes

When transitioning from one react route to another, I can pass strings via URL parameters
this.props.history.replace({
pathname: `${this.props.match.url}/newRoute`,
search: '?someData=someValue'
})
But what if I want to pass something more complex, e.g. a deeply nested JSON object? Is there a way such data can be passed from one route to another without using Redux?
The project in question is using react 14.4.2 and react-router 4.3.1.
Passing complex data between routes is usually a bad idea. For example, what happens when the user refreshes the page on the route? When the page reloads, all the app state and whatever you passed through to the route will have been lost.
As a rule of thumb, each route should be capable of gathering or refreshing all of the data it needs from scratch using only the information in the url (paths, path params, or query strings).
If that doesn't quite fit with your use case, consider the possibility that this part of your app doesn't need its own bookmarkable route/url, and you're just implementing a transitory UI change which is ok if it gets lost upon page refresh.
Example for passing parameter in Route:
<Route path="/:id" component={Child} />
const Child = ({ match }) => (
<div>
<h3>ID: {match.params.id}</h3>
</div>
);
please refer : https://reacttraining.com/react-router/web/example/url-params
I think possible ways can be:
1- By using location state object, we can pass the data into that and it will be available in new page. Issue with this approach will be, after refreshing the new page, that data will not be available.
Like this:
this.props.history.replace({
pathname: `${this.props.match.url}/newRoute`,
search: '?someData=someValue',
state: {
....
}
})
And access it in new page by: this.props.location.state
2- Using localStorage, set the data into localStorage before navigating to new page and then read the same data in new page.
Like this:
navigate() {
localStorage.setItem('data', obj);
this.props.history.replace({
pathname: `${this.props.match.url}/newRoute`,
search: '?someData=someValue',
})
}
I will suggest to use the 2nd approach, its not a good idea of passing big object in route.

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

Using react-router-redux can Link components be used to transition after loading a routes state

I have been using react-router-redux which allows for my location to be stored in state nicely, and allows me to use react-router for defining routes, and linking between routes.
One problem seems to be that when I need to navigate using a <Link /> component like so:
<Link to={`/movie/${m.id}`}>
<p>{m.name}</p>
</Link>
this will trigger the change of route, and on load of the new route, in this case the <Movie /> route, the state for the movie page is loaded.
This doesn't really work nicely for transition between pages, as the new page will be blank on navigation, or you can put in a loader but really it should not navigate to the new page until the api call for the new page has loaded. Similar to sites like pitchfork.com or theguardian.com
This implementation has been discussed in this question and the approach of just using a loader between page transitions is used in the real-world-example and reddit api example
My question is, if I want to load state for the new container/route in my site before navigating, do I need to follow the approach suggested by dax chen in this question?
To sum up the answer was to fire an action and using redux-thunk we can preload the state by calling the action for the new container/route, then on completion we navigate to the page using react-router-redux's push() action
store.dispatch(push('/foo'))
If this is the recommended approach for preloading a page before transition, then I will be changing my react-router-redux-example and my current site to use this approach.
An action to load the new route's data then navigate will look something like this:
function fetchInit(url, id, to) {
return dispatch => {
return dispatch(fetchMovie(url, id))
.then(function(home) {
return dispatch(push(to))
})
}
}
It will need some logic to identify the new container's action to call and also the new path to navigate to. It may call the static fetchData() method on the new page's container component.
So this will somewhat complicate the <Link /> component, I am just looking for some advice on if this is the correct way, or I may have missed a simpler more recommended approach.
<Link onClick={ () => dispatch(navigate(`/movie/${m.id}`, 'Movie')) } >
<p>{m.name}</p>
</Link>
I made an implementation that solves this, and it is possible to make any type of custom link component and have it act in any way you like, however it is probably not a good idea for it to wait for a long load event before changing to a route, as users want the route transition to be immediate. The better approach is to use a nice loader and mix it in to the design of your page well, similar to what is done on pitchfork.com and also to ensure data comes back from the API quickly using caching e.g. redis, memcached.
Here is the library I made showing a custom react-router-redux link component: https://github.com/StevenIseki/react-router-redux-link

Resources