Can I set a base route in react-router - react-router

Assuming my app's base url is example.com/app
Is it possible to set a base route in react-router so instead of writing all routes as
/app/a
/app/b
/app/c
I can just specify them as
a
b
c
I tried the below example I found in the docs but it wouldn't work (page would display nothing). Maybe it's because I'm using react-router#3.0.0-alpha.1, or I'm doing something wrong.
import { useRouterHistory } from 'react-router'
import { createHistory } from 'history'
const history = useRouterHistory(createHistory)({
basename: '/app'
})
const Root = ({store}) => (
<Provider store={store}>
<Router history={history}>
<Route path='/' component={App}>
...
</Route>
</Router>
</Provider>
)

With the newest react router (v4) you can easily do this
<BrowserRouter basename="/calendar">
<Link to="/today"/> // renders <a href="/calendar/today">
</BrowserRouter>

If you want to use <Router />, that give you access to history object, allowing you to change page through history.push('/my-path') method directly from js. You will face the issue that BrowserRouter does not have history prop available, and Router does not have basename available.
The solution is the following:
const App: React.FC = () => {
// do not put a slash at the end of the basename value.
const history = createBrowserHistory({ basename: '/your-base-name' });
return <Router history={history}>
...
</Router>;
}
The base URL for all locations. If your app is served from a sub-directory on your server, you’ll want to set this to the sub-directory. A properly formatted basename should have a leading slash, but no trailing slash
https://reacttraining.com/react-router/web/api/BrowserRouter/basename-string

Related

React Router how to redirect path to path with query params and multiple query params to same component?

I have a component called TableComponent. I want to redirect path tables/:table to tables/:table?tab=tab1 and all different tabs use the same TableComponent.
For example:
tables/:table?tab=tab1 -> TableComponent
tables/:table?tab=tab2 -> TableComponent
tables/:table?tab=tab3 -> TableComponent
I have the code below but it doesn't work:
<Redirect
exact
from="/tables/:table"
to={{
pathname: "/tables/:table",
search: "?tab=tab1"
}}
/>
<Route
path="/tables/:table" <- how can I make this route ignore the query params?
component={TableComponent}
/>
Or should i do the redirect inside the TableComponent?
Edited:
Add a parent component above the Table Component and check if there are query params in the URL using React Router's useHistory. If there are none, use the useHistory hook and do history.push(tables/:table?tab=tab1).
Original answer:
I don't think you need the redirect. Use the route to take you to the TableComponent.
<Route
path="/tables/:table"
component={TableComponent}
/>
When the user clicks a link that should direct to a certain tab, add the query param to the URL, like so:
<Link to={`tables/${table}?tab=${tab}`}
The URL would be something like tables/august-finances?tab=1 or whatever.
In the tables component, use the useHistory hook to see if there's a search parameter and what it is. This is a built-in prop of the hook. Anything after the ? in the URL will be viewable from history.
Ex.
import { useState, useEffect } from 'react;
import { useHistory } from 'react-router;
const TableComponent = () => {
// create state for the tab and set it to null
const [tab, setTab] = useState(null);
let history = useHistory();
useEffect(() => {
// this will be `tab=tab1`
const queryString = history.search;
//isolate the tab by splitting the string by the = character
// it will create array ["tab","tab1"], get the "tab1"
const currentTab = queryString.split("=")[1];
setTab(currentTab);
},[history]);
return(
// if !tab, return nothing
// if tab does have a value, use a conditional expression to return which tab of the table should be shown first
)
}

redux-observable perform transition once the action fires

I am using redux-observable with an epic.
return action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }), Observable.of({ type: 'APPLIED_SHOPPING_LISTS' }).delay(5000);
Once the epic finishes firing the 'APPLIED_SHOPPING_LISTS' I want to perform a transition, I am using react-router. What is the best place to do this? I saw redux-history-transitions, is this an add-in I should be using?
Further to add to this, I did use redux-history-transitions and change this to the following
return action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }), Observable.of({ type: 'APPLIED_SHOPPING_LISTS', meta: {
transition: (prevState, nextState, action) => ({
pathname: '/Shopping',
}),
} }).delay(5000);
This does seem to change the url and transition to happen, however the component I have configured under the '/Shopping' path does not render. It just stays on the current page. This is what my route looks like
<Route path='/' component={App}>
<IndexRoute component={LoginContainer} />
<Route path='login' component={LoginContainer} />
<Route path='landing' component={LandingComponent} />
<Route path='Shopping' component={ShoppingPathComponent} />
</Route>
If you're using react-router v3 or before, you can use middleware that will let you push/replace history with redux actions, like react-router-redux. I'm not familiar with redux-history-transitions, but it may (or may not) work similarly.
With react-router-redux, it would just mean emitting the action you want to transition the history, when you want it to happen.
So you'd import the push action creator, and just add it as another action after APPLIED_SHOPPING_LISTS:
Observable.of(
{ type: 'APPLIED_SHOPPING_LISTS' },
push('/Shopping')
)
Altogether, something like this:
import { push } from 'react-router-redux';
const somethingEpic = action$ =>
action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(
Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }),
Observable.of(
{ type: 'APPLIED_SHOPPING_LISTS' },
push('/Shopping')
)
.delay(5000)
));
If you're using v4, as of this writing react-router-redux is not yet compatible with it, but it's in active development here.

React-Router confused about my lifecycle router

After checking out this react-router tutorial, I tried to integrate what I learned here into my project.
My scenario is similar to the number 2 from the tutorial, except that when the user enters /, I want to fetch an api and redirect to the first category comming from the api that looks like this [{'category':'electronics', 'items':[{..}],..},..]
my router looks like this
import RoutaZ from 'Routes.js';
...
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRedirect to={RoutaZ.state.data[0].name} />
<Route path=":category" components={Container, SideNavigation} />
</Route>
my Routes.js looks like this
let Routes = React.createClass({
getInitialState () {
return {
data: null
}
},
componentDidMount() {
var self = this;
fetchData().then(function(results){
self.setState({data: results.data});
})
},
render() {
/* want to return the the first category from api */
return this.state.data[0].name
}
});
In the router, RoutaZ.state.data[0].name returns undefined because the initial state is null. If I set the initial state to [{'category':'hello',...}], it returns hello and redirects to the hello page as expected. What can I do to redirect after the db is fetched?
1) How can I use onEnter from react-router with my current config?
2) How and where can I set a parent component to my router handle all the fetching and pass it to the router as a child?
EDIT: This is just a little part of my application,which is finished but I only have this redirect issue. I know that I can use redux-saga but all my application is already done and would have to redo it completely which I cannot afford.
1- I tried using onEnter but don't know where I should place it.
2-Somewhere in my application is fetched data from the parent component and gave it as props to the child and the child received the data from the api.
Solved it. I had to push the results when the component mounted
componentDidMount() {
var self = this;
fetchData().then(function(results){
router.push(results.data[0].category);
})
},

In React Router, how do you pass route parameters when using browser history push?

Using the newer version of React Router, how do you pass parameters to the route you are transitioning to if you are transitioning using the browserHistory.push()? I am open to using some other way to transitioning if browserHistory does not allow this. I see that in my top most child component in React Router there is this.props.routeParams but I can't seem to get this populated with values I want from the previous route when transitioning.
Now you will have do something like this
history.push({
pathname: '/about',
search: '?the=search',
state: { some: 'state' }
})
Here is the link for API documentation
for react router v4 we can do things this way:
const history = useHistory();
history.push({
pathname: "/create-opinion/"+user,
state:{ username:userName}
};
and to get it from the other component simply:
const history = useHistory();
then to get username:
const username = history.location.state.username;
don't forget to import useHistory from react-router-dom
if you haven't install react-router-dom
yet type:
$ npm install --save react-router-dom
If you want to pass a path parameter you can do it as follows. I am using sample component Merchant.
Define Route
<Route exact path="/merchant/:id" render={(props) => <Merchant id={props.match.params.id}/>} />
Define History
const history = createBrowserHistory({
basename:''
})
Add to history with parameter
history.push(`/merchant/${id}`)

react router, match wildcard as parameter

I have a legacy web app that is being replaced with React and it is called by these various systems by being passed a URL (derived from records in and existing DB system) The app renders folder views, of files in a sandboxed container according to the path passed in.
I need to be able to identify these requests and route them to a File/Folder handing page, treating everything after the '/files/ part of the path down as a parameter to the path of the file or folder.
<Route path="/" handler={Master}>
<Route name="files_link" path="files" handler={Files} />
<Route name="filesWithPath_link" path="files/*:path" handler={Files} />
</Route>
I would like to be able to handle requests passed into
/files
and have it handled in the page (defaulting to top level folder because no path parameter passed.
and have all the following examples of possible URL just passed to the router and extract the path parameter from the bit after /files/.
/files/folder path=folder
/files/folder/filename.ext path=folder/filename.ext
/files/folder/folder1 path=folder/folder1
/files/folder/folder1/filename.ext path=folder/folder1/filename.ext
/files/folder/folder1/folder2/ path=folder/folder1/folder2
/files/folder/folder1/folder2/filename.ext path=folder/folder1/folder2/filename.ext
.... and so on ...
When I try this I get an error
Uncaught Invariant Violation: Missing "splat" parameter for path "/beta/page/files/*:path"
I'm currently using react 0.14.2 and react-router 0.13.4.
How would you go about handling a variable length path in this manner in React-router?
You need to use it like so:
<Route name="filesWithPath_link" path="files/*" handler={Files} />
And then in your React component you can access the value of the splat:
// The '*' in the path match gets assigned to this.props.params.splat
var path = this.props.params.splat;
For completeness, the splat parameter can be accessed from the useParams() hook as property 0:
const splat = useParams()[0];
In React Router v6, the "*" can be accessed from useParams() as a property named *.
const wildcard = useParams()["*"];
And of course you can always get the entire URL (i.e. everything including the bit before the *) using useLocation()
Example in TypeScript with a named parameter:
<Route path="/files/:path">
<Files/>
</Route>
import { useParams } from 'react-router';
export function Files(): React.ReactElement {
const splat = useParams<{ path: string }>().path;
}