React Routing with Hooks To Pass Dynamic Prop to Route - reactjs

I'm trying to pass a default prop to a Route with React that I want to change dynamically when I call the Route later to access the nth item of a list instead of the 0th.
The Route is declared in App.js like so:
<Route
exact
path="/myurl"
render={(props) => <MyComponent{...props} startAtStep={0} />}
/>
Then when I trigger that route with useHistory() I want to pass in a different number - say
const handleEdit = (newIndex) => {
setTimeout(() => {
push('/myurl', { startAtStep: newIndex});
}, 220);
};
This, however, does not work - what am I doing wrong?

You'd be better off using useRouteMatch in this case I think:
const handleEdit = (newIndex, props) => {
let match = useRouteMatch({
exact: true,
path: '/myurl'
});
return (
<div>
{match && <MyComponent {...props} startAtStep={newIndex} />
</div>
);
};

The route component that should be dynamic can be wrapped with
export default withRouter(MyComponent);
Now at the component { props.locaction } holds dynamic state obj, that can be modified from the navigation caller
<Link
to={{
pathname: '/',
state: { startAtStep : n }
}}
>

Related

In React, why route can work properly without path

code as below. {...rest} doesn't contain path, the route shouldn't route properly, but in fact it can route to path(/movies/:id). Why could that happen?
<ProtectedRoute path="/movies/:id" component={MovieForm} />
const ProtectedRoute = ({ path, component: Component, render, ...rest }) => {
console.log("rest: ", { ...rest });
return (
<Route
// path={path}
{...rest}
render={(props) => {
console.log(props);
if (!auth.getCutterntUser()) return <Redirect to="/login" />;
return Component ? <Component {...props} /> : render(props);
}}
/>
);
The reason the route still works is because you are specifying a path prop on the PrivateRoute component and this is the component the Switch uses for path matching.
A review of the v5 Switch source code:
/**
* The public API for rendering the first <Route> that matches.
*/
class Switch extends React.Component {
render() {
return (
<RouterContext.Consumer>
{context => {
invariant(context, "You should not use <Switch> outside a <Router>");
const location = this.props.location || context.location;
let element, match;
// We use React.Children.forEach instead of React.Children.toArray().find()
// here because toArray adds keys to all child elements and we do not want
// to trigger an unmount/remount for two <Route>s that render the same
// component at different URLs.
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
return match
? React.cloneElement(element, { location, computedMatch: match })
: null;
}}
</RouterContext.Consumer>
);
}
}
The Switch iterates over its children and checks for a path or from prop path value, and computes a match. For the computed match it clones and returns the matched element.
<ProtectedRoute
path="/movies/:id" // <-- child component path specified here
component={MovieForm}
/>
In fact, even something as simple as a div with a path prop will work, example:
<div path="/movies/:id">TEST</div>
This only explains the matching within the Switch, but now you're wondering why the Route component being rendered by PrivateRoute still works. This is because any Route not directly rendered by a Switch is now inclusively matched and rendered (as if it were only in a Router component). The Route has no path so it matches anything and is rendered.
It should be sort of obvious now that the Route component itself is rather irrelevant. You can simplify the PrivateRoute a bit; conditionally render a Route with all the route props passed through, or the Redirect to login page.
const ProtectedRoute = (props) => {
return auth.getCutterntUser()
? <Route {...props} />
: <Redirect to="/login" />;
};

Why browser back button returns a blank page with the previous URL

On pressing the browser back button why does an empty-blank page is displayed instead of the component that I'd visited before? Only the URL is getting changed to the previous one. Using React Router v5
That is really frustrating, how can I fix this ?
SignUp.js
render() {
return (
<div className='signUp-div'>
<Header />
<Router history={history}>
<div className='form-div'>
<Redirect to='/signup/mobile' /> // Default page
<Switch>
<Route exact path={'/signup/mobile'} component={MobileNum} />
<Route exact path={'/signup/idnumber'}>
<IdentNumber setPersonalID={this.props.setUserNumber} />
</Route>
<Route exact path={'/signup/password'}>
<CreatePass
setIfSignUp={this.props.setIfSignUp}
/>
</Route>
</Switch>
</div>
</Router>
</div>
);
}
IdentNumber.js
const IdentNumber = ({setPersonalID}) => {
const handleCheckID = () => {
history.push('/signup/password');
}
return (
<div className='form-div'>
<button
onChange={(event) => onChangeHandler(event)}
> Page password</button>
</div>
);
};
export default IdentNumber;
Did I explain it right ?
Thanks
From the code sandbox link, I've observed a few things that could potentially cause this issue.
Update your imports from import { Router } from "react-router-dom";
to
import { BrowserRouter as Router } from "react-router-dom";
<BrowserRouter> uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.
The routes will remain the same. You're using react-router-dom v5.2.0, you could use useHistory to get the history object. useHistory simplified the process of making components route-aware.
With my changes: https://codesandbox.io/s/suspicious-pine-5uwoq
We don't need exact key for all routes other than the default "/" when it is enclosed in Switch and placed in the end. But exact matches /signup/mobile and /signup/* as same. Switch renders only one route and whichever route is matched first.
An example project for reference.
And if you want to handle the back button event yourself, follow the below examples.
In a function component, we can handle the back button press by listening to the history object.
import { useHistory } from 'react-router-dom';
const Test = () => {
const history = useHistory();
useEffect(() => {
return () => {
if (history.action === "POP") {
}
};
}, [history])
}
listen to history in useEffect to find out if the component is unmounted. history.listen lets us listen for changes to history.
Example:
import { useHistory } from 'react-router-dom';
const Test = () => {
const history = useHistory();
useEffect(() => {
return history.listen(location => {
if (history.action === 'POP') {
}
})
}, [])
}
react-router-dom now has Prompt,
import { Prompt } from "react-router-dom";
<Prompt
message={(location, action) => {
if (action === 'POP') {
// back button pressed
}
return location.pathname.startsWith("/test")
? true
: `Are you sure you want to go to ${location.pathname}?`
}}
/>

React router is not mounting the component

I'm using React Router for routing to different routes as below:
<Router>
<Switch>
<Route path="/teams/:teamName/matches" >
<MatchPage/>
</Route>
<Route path="/teams/:teamName" >
<TeamPage/>
</Route>
</Switch>
</Router>
Now in my TeamPage component I'm calling an API using async and then in the render method invoking another component called MatchDetailCard
class TeamPage extends Component {
constructor(props) {
console.log('const called')
super(props)
this.state = {
team: [],
teamName:null
}
}
async componentDidMount() {
console.log(this.props.match.params.teamName);
const teamName = this.props.match.params.teamName;
const response = await fetch(`http://localhost:8080/team/${teamName}`);
const json = await response.json();
console.log(json);
this.setState({team:json, teamName: teamName});
}
componentDidUpdate () {
console.log('updated')
}
render() {
if (!this.state.team || !this.state.team.teamName) {
return <h1>Team not found</h1>;
}
return (
<div className="TeamPage">
<div className="match-detail-section">
<h3>Latest Matches</h3>
<MatchDetailCard teamName={this.state.team.teamName} match={this.state.team.matches[0]}/>
</div>
</div>
);
}
}
export default withRouter(TeamPage);
Within the MatchDetailCard component I create a router Link to the same TeamPage component which will have a different teamName this time as below:
const MatchDetailCard = (props) => {
if (!props.match) return null;
const otherTeam = props.match.team1 === props.teamName ? props.match.team2 : props.match.team1;
const otherTeamRoute = `/teams/${otherTeam}`;
const isMatchWon = props.teamName === props.match.matchWinner;
return (
<div className={isMatchWon ? 'MatchDetailCard won-card' : 'MatchDetailCard lost-card'}>
<div className="">
<span className="vs">vs</span><h1><Link to={otherTeamRoute}>{otherTeam}</Link></h1>
</div>
</div>
);
}
export {MatchDetailCard};
The problem that I'm facing is that the on-click of the link to /team/teamName route only the TeamPage component is not mounting instead it's just getting an update.
I want to have the call to componentDidMount hook to make the API call again in this scenario.
What's the problem with my logic?
If the same component is used as the child of multiple <Route>s at the same point in the component tree, React will see this as the same component instance and the component’s state will be preserved between route changes. If this isn’t desired, a unique key prop added to each route component will cause React to recreate the component instance when the route changes.
https://reactrouter.com/web/api/Route
You can add the teamName as a key prop on the component, which will tell React to unmount/mount the component when the key value changes.
<Router>
<Switch>
<Route
path="/teams/:teamName/matches"
render={({ match }) => {
return <MatchPage key={match.params.teamName} />;
}}
/>
<Route
path="/teams/:teamName"
render={({ match }) => {
return <TeamPage key={match.params.teamName} />;
}}
/>
</Switch>
</Router>

Read URL parameters

I have a very basic app and I want to read the request parameter values
http://localhost:3000/submission?issueId=1410&score=3
Page:
const Submission = () => {
console.log(this.props.location); // error
return ();
}
export default Submission;
App
const App = () => (
<Router>
<div className='App'>
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/submission" component={Submission} />
<Route path="/test" component={Test} />
</Switch>
</div>
</Router>
);
export default App;
Did you setup correctly react-router-dom with the HOC in your Submission component ?
Example :
import { withRouter } from 'react-router-dom'
const Submission = ({ history, location }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Click Me!
</button>
)
export default withRouter(Submission)
If you already did that you can access the params like that :
const queryString = require('query-string');
const parsed = queryString.parse(props.location.search);
You can also use new URLSearchParams if you want something native and it works for your needs
const params = new URLSearchParams(props.location.search);
const foo = params.get('foo'); // bar
Be careful, i noticed that you have a functional component and you try to access the props with this.props. It's only for class component.
When you use a functional component you will need the props as a parameter of the function declaration. Then the props should be used within this function without this.
const Submission = (props) => {
console.log(props.location);
return (
<div />
);
};
The location API provides a search property that allows to get the query parameters of a URL. This can be easily done using the URLSearchParams object. For example, if the url of your page http://localhost:3000/submission?issueId=1410&score=3 the code will look like:
const searchParams = location.search // location object provided by react-router-dom
const params = new URLSearchParams(searchParams)
const score = params.get('score') // 3
const issueId = params.get('issueId') // 1410

hide id or query string while passing through react router

i am having route where i pass id,but i dont want to show id in url,
`<Route path={`${match.url}invite-members/:groupID`} exact component={InviteMembers} />`
this gets converted in url https://local..../invite-members/5,
but instead of that i want https://local..../invite-members, but the functionality should remain the same as in i get id in invite-members through this.props.match.params.groupID should be as it is,please help
using react router "react-router-dom": "^4.2.2",
If you want to change url to '/invite-members', you can add the Redirect component. And in case you want to save groupId, you could save it to your component state:
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import {
Router,
Route,
Link,
Switch,
Redirect
} from "react-router-dom";
class Root extends PureComponent {
// add groupId field to your component
// In case you use redux or any another state management library, you can save groupId to store
state = { groupId: null };
render() {
const { store, history } = this.props;
// just for example I defined '/' path to redirect on /invite-members url
return (
<Router>
<Switch>
<Route
path="/"
exact
render={props => (
<Redirect
to={{
pathname: "/invite-members/123",
state: { from: props.location }
}}
/>
)}
/>
<Route
path="/invite-members"
exact
render={props => (
<InviteMembers {...props} groupId={this.state.groupId} />
)}
/>
<Route
path="/invite-members/:groupID"
exact
render={props => {
return (
<RedirectAndSaveGroupId
{...props}
groupId={props.match.params.groupID}
onSetGroupId={groupId => {
this.setState({ groupId });
}}
/>
);
}}
/>
</Switch>
</Router>
);
}
}
export default Root;
class RedirectAndSaveGroupId extends PureComponent {
componentDidMount() {
// save groupId to Root component
this.props.onSetGroupId(this.props.groupId);
}
render() {
// redirect to /invite-members without groupId
return (
<Redirect
to={{
pathname: "/invite-members",
state: { from: this.props.location }
}}
/>
);
}
}
// Just for demo. In this.props.groupId we can receive groupId
class InviteMembers extends PureComponent {
render() {
return this.props.groupId;
}
}
Note, that in case you using any state management library such as Redux, you can store group id in them
I maybe have a very simple solution :
Router link :
<Link to={{pathname: '/item/'+name, state : {id}}}>{name}</Link>
In the Targeted file :
state = this.props.location.state
QueryParameters = () => {
const id = this.state.id
return { id }
}
And launch your query requiring the ID. It does not appear in the url.
Passing data in the params object will always result in that data being shown in the URL. Because the params object is built from the url.

Resources