Good day, hoping that I am not bothering you guys.
Details
I have a path like this
localhost:3000/Project-1/todo-1
and Switch of Route like this
<Route path='/:project/:todo' component={Todo} />
Expected Output
When browser is in the example path, what I expected is App.js can also get params object by using useParams but shows empty. Did I misuse the hook? Thank you for the answers.
Additional Details
I did use useLocation that returns something like path but that is not my aim, it should be something like
params: {
project: 'Project-1',
todo: 'todo-1 '
}
that is returned using useParams for ease of extraction of project and todo values
I believe Route injects props to components it renders, so you should be able to pull it straight from the match prop within Todo: this.props.match.params.
You’ll have access to match objects in various places:
Route component as this.props.match
Route render as ({ match }) => ()
Route children as ({ match }) => ()
withRouter as this.props.match
matchPath as the return value
https://reacttraining.com/react-router/web/api/match
This only works for components rendered within a router on a route, i.e. the component a Route is rendering, or a child component further down the DOM tree. If App.js is rendering the router it won't have it.
If you need to do something with route params in the root App.js you can wrap it in a route, with unspecified path so it matches all routes, in your router using the render prop. Just ensure this is rendered outside any Switch components though as switches only return the first match, not all matches.
<Route
render={({ match }) => {
console.log('match', match);
// do what you need with the `match` prop, i.e. `match.params`
return null; // return null to indicate nothing should actually be rendered
}}
/>
Related
<Route path="/pointandclick">
<MyComponent />
</Route>
For this piece of code, is there any way for my component to get the path of the Route that has been hit?
edit: to be more precise, let's say I want to know the string that has been hit (in this case /pointandclick) because in MyComponent I want to route between other paths, so I have to know if which path do I come from.
E.g.
<Route path="/pointandclick">
<MyComponent />
</Route>
<Route path="/draggablegame">
<MyComponent />
</Route>
and in my component I want to route on other components depending on the path. (example: if the Route that has been hit is pointandclick I want to render between game1, game2, game3 and if the Route is draggablegame I want to render between drag1, drag2, drag3 - so using the location hook might not be the best thing I think.
You're looking for a simple javaścript's window.location.pathname.
The useLocation hook returns the location object that represents the current URL. You can think about it like a useState that returns a new location whenever the URL changes.
import { useLocation } from 'react-router-dom'
function MyComponent() {
let location = useLocation();
console.log(location);
}
With useLocation(), you can get the active route. It's a hook that returns the location object that contains information about the current URL. Whenever the URL changes, a new location object will be returned.
Demo: https://codesandbox.io/s/use-location-demo-7d3c3
Read on: https://www.kindacode.com/article/react-router-uselocation-hook-tutorial-and-examples/
I have created an <Authorise /> component which renders either a 'yes' or 'no' prop based on a boolean 'allowed' prop.
export const Authorise = ({
allow = false,
yes = () => null,
no = () => null
}) => (allow ? yes() : no());
<Authorise /> component works as expected however when I try to render a <Route />, only the first route is rendered and the rest of the <Route /> components are ignored.
Why is this happening? If I render the routes outside of the <Authorise /> component it works fine. Only when rendering the components via the <Authorise /> component does it not work and I can't work out why.
Check out my example to see the issue:
CodeSandbox
It's because of
https://reactrouter.com/web/api/Switch/children-node
All children of a <Switch> should be <Route> or <Redirect> elements. Only the first child to match the current location will be rendered.
If you're using a library but not playing by its rule, then something like this is what was expected to happen.
The issue is not with your Authorize function, but because of the Route Component which will only display when the path is matched. The second issue it with Switch which will make it that only the first matched route to be displayed
With Switch added the "/" path will always be hit since it's the first one. If you try to remove your Switch (or move the route with "/" at the end) and go to "/next" on your local dev then the /next route will be displayed
I'm using react-router-dom and this is currently how it looks:
<Switch>
<Route path={"/layouts/:layoutID"} component={Layouts} />
<Route
path={"/dashboard/:dashboardID"}
component={Dashboards}
/>
</Switch>
When the user navigates to "/dashboard/:dashboardID" inside this component he can choose a sub-page onClick, and I want the URL structure will be "/dashboard/:dashboardID/:pageID" pageID will navigate to 'PageIdComponent' this component will get a 'match' props and will show pageID
Please see the attached file that shows the necessary structure.
What is the best way to implement it?
If you have the child route inside your Dashboard component, then you can use the match.path value to build up a sub route:
<Route path={`${props.match.path}/:dashboardId`}>} component={SubDashboard} />
in order to make the path match what the parent's had and then add a new variable on top of that.
Then you can navigate to it using the match.url to make a link to the current URL and add the subPage to it:
<Link to={`${props.match.url}/pageFour`}>Page four</Link>
Instead of prop driling, React Router offers ways to get the relevant props to whatever components you desire via withRouter HOC or various hooks. The hooks were introduced in version 5.1
To get the match via a hook you can use const match = useRouteMatch()
To get the relevant props via the HOC:
const Example = useRouteMatch(function Example({ match, location, history }) {
return <div>{match.url}</div>;
});
For react router v6 (that's upcoming):
https://reacttraining.com/blog/react-router-v6-pre/#nested-routes-and-layouts
I am using react-router-redux and I'm trying to update the header of my app, that receives it's state from the store, whenever the route changes (##router/UPDATE_LOCATION)
Currently I'm dispatching actions in a componentWillMount like:
componentWillMount() {
this.props.appActions.setHeader('New Block')
}
When I manually set the header in componentWillMount on route /blocks/new, and it is a child of a route 'blocks', who both have a different header, it doesn't work when I go back in history, because the component of route blocks does not mount again, it is still mounted. Thus the header is still New Block. And not what its own header was before, when blocks mounted, and new was still unmounted as child.
(And when I try to reverse time with the redux-devtools, what seems to happen then, every time I go back to a point where a component mounts again, it will dispatch the action again, and the devtool will receive another dispatch.)
The routes:
<Route path="begin" component={PlayerBeginContainer}>
<IndexRoute component={PlayerOverview}/>
<Route path="blocks" component={PlayerBlocks}>
<Route path="new" component={PlayerNewBlock}/>
</Route>
</Route>
...
I've tried to sync the store whenever a route changes, but:
if (action && action.type === UPDATE_LOCATION) {
let path = action.payload.pathname.split('/')
// Laboriously iterate through array to figure out what the new header state should be.
// i.e. if (1 in split && split[1] === 'routeName')
// or let lastPath = path[path.length - 1]
// and getting parentPath would require more checking of whether it is the parent itself or not etc.
// appHeader = 'routeHeader'
return Object.assign({}, state, { appHeader: appHeader});
}
This gets very tedious when you just need it to trigger on a specific sub-route,
And I want to avoid making another nested structure, while I already have that defined in the router.
In the header I can't use anything other than this.props.location.pathname either to try and figure out which route i'm on, and the components themselves should not bother with setting the header themselves (i.e. in componentWillMount).
Last option would be to use the router onEnter, but I'd like to keep the router clean, but perhaps I need to compromise on this.
Is there something I'm missing here? Or some sort of lib that can help me with this?
TL;DR: How can I make my header component aware of which route we are on, without having to break down the location.pathname to figure out where we are?
This is code from one of my codebases. In this app i use hash history, but i think you could do same thing with other history objects too.
import {hashHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
const history = syncHistoryWithStore(hashHistory, store);
history.listen(location => {
// here you can do something after location is updated
});
<Router history={history}>
....
</Router>
and then in you components you can get some info from state.routing:
function mapStateToProps(state) {
return {
current: state.routing.locationBeforeTransitions.pathname,
};
}
export default connect(mapStateToProps)(App);
To change route from some component, do this:
import {push} from 'react-router-redux';
this.props.dispatch(push('/route'));
I am doing something like:
<Router history={browserHistory}>{routes}</Router>
When I do above whenever URL in address bar changes call is going to server but this is not what I want, I want first time page to load from server but after that whenever route change component should load in client side only. Am I missing something here?
In client side I am doing something like :
ReactDOM.render(
<Provider store={app.store}>
<Router history={browserHistory}>{routes}</Router>
</Provider>,
document.getElementById("app")
);
and my routes look like:
const routes = (
<Route path="/" component={DJSAppContainer}>
<Route path="page" component={DJSPage}>
<Route path="/page/:pageName" component={PageContainer} />
</Route>
</Route>
);
Now whenever I do location.href = "/page/xyz" it goes to server and load the content.
You shouldn't change location.href directly. You should send the new path to React using:
ReactRouter.browserHistory.push(newPath);
If you have anchor tags, you should use the <Link> component mentioned in #ahutch's answer.
React router exposes as props the history used in the current views. See their docs for all the props that are injected here.
If you want to redirect from DJSAppContainer or any of the two children views DJSPage or PageContainer you could do it by accessing the history property:
const {history} = this.props;
history.pushState('/path/to/new/state');
If on the other hand you do some fancy stuff and want to redirect from outside the components, try this (taken from react router docs)
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
This two approaches assume you are trying to redirect based on some complex logic, not as a consequence of a click event. If you just want to handle click events, try using the <Link/> component of react-router.
It seems that the history property is now deprecated, and seems to be replaced by the router context property as seen in the implementation of the link component. Basically, if you really want your code to be future proof, you should ask for the contextType:
contextTypes: {
router: object
},
And then use it from the context of the component:
this.context.router.push('/some/path')
Assuming you're using node on the back-end, make sure that you have it set up according to the react-router docs.
// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})
Also, when you're linking between components and you want to ensure that you are using react-router for the routing instead of the server, make sure to use Link. Hope that answers your question.
I was having a similar issue, while hashHistory worked without issue browserHistory would always load from the server. I got things working by remembering to call preventDefault(). Here is my function:
handleSubmit: function (e) {
e.preventDefault();
var username = this.usernameRef.value;
this.usernameRef.value = '';
this.context.router.push(`/profile/${username}/`);
}
handleSubmit is called on a form onSubmit.