In React, why route can work properly without path - reactjs

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" />;
};

Related

Change window location when rendering component with React?

Can i change window location when react component going to be rendered without use react router <Route /> ?
{
this.state.loading ?
( <Loader percentageLoadingProgress={this.state.percentageLoadingProgress} loadingInformation={this.state.loadingInformation} /> ) :
( <Companies {...this.state} /> )
}
I need <Companies /> to be rendered with changed window location
You can either return a Redirect, eg:
const someComponent = props => {
if (props.foo) return `<Redirect to="/somewhere/else" />`;
return <NormalUI />;
}
Or you can use the useHistory hook to get a history object, and then call history.push('/somewhere/else'):
const someComponent = props => {
const history = useHistory();
if (props.foo) history.push('/somewhere/else');
else return <NormalUI />;
}
The Redirect component is the preferred way.

React Routing with Hooks To Pass Dynamic Prop to Route

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 }
}}
>

Prevent react route unmounting component on state change

I'm using react-router (v.4.3.1) to render the main part of my application and I have a drawer on the left side with the menu. When a button is toggled in the app header I'm changing the state of the collapsed variable so that the components re-render the css accordantly. My problem is this variable needs to be stored on the component rendering all my Route and when the component is updated Route is unmounting and mounting it's component.
I've already tried to provide a key to my Route but it's not working.
My code looks like this and the parent of this component is the one being updated which re-renders my Main component:
class Main extends Component {
constructor(props) {
super(props);
this.observer = ReactObserver();
}
getLayoutStyle = () => {
const { isMobile, collapsed } = this.props;
if (!isMobile) {
return {
paddingLeft: collapsed ? '80px' : '256px',
};
}
return null;
};
render() {
const RouteWithProps = (({index, path, exact, strict, component: Component, location, ...rest}) =>
<Route path={path}
exact={exact}
strict={strict}
location={location}
render={(props) => <Component key={"route-" + index} observer={this.observer} {...props} {...rest} />}/>
);
return (
<Fragment>
<TopHeader observer={this.observer} {...this.props}/>
<Content className='content' style={{...this.getLayoutStyle()}}>
<main style={{margin: '-16px -16px 0px'}}>
<Switch>
{Object.values(ROUTES).map((route, index) => (
<RouteWithProps {...route} index={index}/>
))}
</Switch>
</main>
</Content>
</Fragment>
);
}
}
I would like the Route just to update and not to unmount the component. is this possible?
you are having this issue due to defining RouteWithProps inside of render method. This causes React to unmount old and mount a new one each time render method is called. Actually creating component dynamically in the render method is a performance bottleneck and is considered a bad practice.
Just move the definition of RouteWithProps out of Main component.
Approximate code structure will look like:
// your impors
const RouteWithProps = ({observer, path, exact, strict, component: Component, location, ...rest}) =>
<Route path={path}
exact={exact}
strict={strict}
location={location}
render={(props) => <Component observer={observer} {...props} {...rest} />}/>;
class Main extends Component {
...
render(){
...
{Object.values(ROUTES).map((route, index) => (
<RouteWithProps key={"route-" + index} {...route} observer={this.observer}/>
))}
^^^ keys should be on this level
...
}
}

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.

Passing part of an url to another function in React

Is there a way for React to take the back of an url user entered and pass it to a function? for example: www.site.com/foobar will pass the foobar to a function.
Essencially what i'm trying to do is to run a check on foobar being in my database inside the checker function, if not there display 404 page not found.
const NotFound = () => (<h1>404.. This page is not found!</h1>)
class App extends Component {
checker : function(e){
if(foobar exists)
//load page with data
else
// {NotFound}
}
render() {
return (
<Router history={hashHistory}>
<Route path='/' component={LoginPage} />
<Route path='*' component={this.checker()} />
</Router>
)
}
}
To expand what I had written in my comment -
class App extends React.Component {
render() {
return (
<Router history={hashHistory}>
<Route path='/' component={LoginPage} />
<Route path='/:token' component={EmailValidation} />
</Router>
)
}
}
class EmailValidation extends React.Component {
state = { checked: false, valid: false }
componentDidMount = () => {
checkToken(this.props.params.token).then((valid) => {
this.setState({ checked: true, valid })
})
}
render() {
const { checked, valid } = this.state
return (
<div>
{ checked
? <div>{ valid ? 'valid' : 'invalid' }</div>
: <div>Checking token...</div> }
</div>
)
}
}
this would be a good use case for an HoC which conditionally renders either the component you want or a 404 page - it would remove the binding between the 404 page and the email validate component (which are only sorta-kinda related)
if you're into using libraries, recompose has a bunch of nice helpers which can accomplish something like this for you.
something else you can do is use react-router's onEnter callback/prop although, iirc, you can't directly access props from that callback.

Resources