React-router and jsx-no-bind - react-router

I'm using react-router to do navigation. I have some components that are requiring a login before they can be seen.
Therefore I have this: (from following documentation in react-router)
<Route
{...rest}
render={props =>
isLoggedIn()
? <Component {...props} />
: <Redirect
to={{
pathname: '/login',
state: {from: props.location},
}}
/>}
/>
However, there is a problem with this, since jsx-no-bind disallows arrow functions. What is the correct way around this? Or should this simply be ignored because it for some reason does not suffer the same performance hits?

Official React documentation on render props:
https://reactjs.org/docs/render-props.html
This documentation uses examples like:
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
Be aware of caveats as mentioned here:
https://reactjs.org/docs/render-props.html#caveats
This includes the use of PureComponents

Related

react-router-dom 6 upgrade help: All component children of <Routes> must be a <Route> or <React.Fragment>

Our application recently updated to the beta versions of react-router-dom, and things were fine. Then when I try to update to 6.0.2, I get lots of invariant errors about All component children of <Routes> must be a <Route> or <React.Fragment>. This is because we have our routes defined as follows:
Feature.jsx:
export const FeatureRoutes = () => (
<Routes>
<Route path="/" element={<Feature1 />} />
<Route path="/*" element={<NotFound />} />
</Routes>
);
routes.jsx:
export const routes = [
{
path: "feature",
component: FeatureRoutes,
},
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
App.jsx:
<Routes>
{routes.map((route) => (
<Route key={route.path} path={`${pathPrefix}/${route.path}/*`}>
<route.component />
</Route>
))}
</Routes>
This now results in the error above, because the inner routes (for example FeatureRoutes) are wrapped in a functional component. I've tried returning the literal JSX but then get another error. I'm not sure how to fix this: is the only answer to completely rewrite how we define our routes? We also have some routes that are stored in the back-end and map to custom components - again I'm not sure how I can wrap these now I'm not allowed to have a component between Routes and Route.
Any advice appreciated.
I believe a small refactor will get your app rendering again.
In the routes array rename component to Component so it can be rendered as a React component, i.e. as a properly named React component (PascalCased).
const routes = [
{
path: "feature",
Component: FeatureRoutes
}
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
When mapping the routes render the Component out on the Route component's element prop as JSX.
<Routes>
{routes.map(({ path, Component }) => (
<Route
key={path}
path={`${pathPrefix}/${path}/*`}
element={<Component />}
/>
))}
</Routes>

How to have react route path one/two/three?

I would like to have something like this
<Route
path="/one/two/three"
render={() => (
<Component/>
)}
exact
/>
But it does not seem to work locally when I wrap it all into a Router. I only see the contents of the Component when it is just /one, but I would like to have /one/two/three as the path (being my root - user lands on that path).
In react-router-v4 you can have below routes only,
<Route path="/one" render={() => (<Component/>)} exact /> //Direct route without any params
or
<Route path="/one/:two" render={() => (<Component/>)} exact /> //Route with params, here `:two` is the parameter to route
If you want to use routes like in your example, then this can be achieve using BrowseRouter basename attribute,
<BrowserRouter basename="/one/two">
<Route path="/three" render={() => (<Component/>)} exact />
</BrowserRouter>
And you Link should be like,
<Link to="/three"/> // renders <a href="/one/two/three">
Few reasons why this did not work for me.
I was missing output: { publicPath: '/' }, in my webpack config.
And weirder thing was that I was using PureComponent instead of Component which totally broke the rending when redirecting to the next step with similar path(x/y/z) or using Link.

react-router render menu when path does not match

I'm using react-router and I want to render a menu component when the user is not in the root and not in the /login path. This is what I have so far
<Route path="/:subpath" component={TopMenuComponent} />
<div>
<Route
exact path="/"
render={props => (
<LoginContainer {...props} setTitle={this.setTitle} />
)}
/>
<Route path='/landing' component={LandingComponent} />
</div>
takes care of not rendering the TopMenuComponent component in the '/' location, however how do I avoid it rendering TopMenuComponent when the user is in the /login path? I could always create another component and wrap it up, but I think that is too much just for this.
Simplest Implementation
Use a ternary expression or short-circuit evaluation to conditionally render your component based on location.pathname, like so:
<Route
render={({ location }) => ['/', '/login'].includes(location.pathname)
? <Component/>
: null
}
/>
Regex Implementation
React Router's matching of path strings relies on path-to-regexp#^1.7.0.
As a result, you can instruct routes to not render for certain paths using regular expressions.
The following implementations should render given any path value, bar "/" and "/login":
// With Regex Inside String.
<Route path={"^(?!.*(\/|\/login)).*$"} component={TopMenuComponent}/>
// With Explicit Regex.
<Route path={new RegExp('^(?!.*(\/|\/login)).*$')} component={TopMenuComponent}/>
Regex in the route path didn't work for me. What worked for me was this. Just add the other condition.
<Route render={({ location }) => {
return location.pathname.indexOf('/login') === -1 ? TopMenuComponent : null
}} />
If you don't wish to use Regular Expressions directly, you can place your login Route in a Switch with the top menu component Route. It will only run the first matching Route and routes without a path attribute match anything.
<div>
<Switch>
<Route
exact path="/"
render={props => (
<LoginContainer {...props} setTitle={this.setTitle} />
)}
/>
<Route path="/:subpath" component={TopMenuComponent} />
</Switch>
<Route path='/landing' component={LandingComponent} />
</div>
For your example, you would need to reorder your divs.
Taken Regex from Arman's answer.
const notInLogin = /^(?!.*(\/login)).*$/
export default () => (
<Router history={history}>
<>
<Route path={notInLogin} component={NavBar} />
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/accounts" component={Account} />
<Route exact path="/profile" component={Profile} />
<Route exact path="/" component={Home} />
</Switch>
</>
</Router>
)
If you get PropsType error: https://stackoverflow.com/a/50439120/1099314
Similar to taylor michels answer, but the following accounts for both the '/login' and the '/' (root) routes:
<Route
render={({ location }) =>
location.pathname !== "/" && location.pathname !== "/login" ? (
<TopMenuComponent />
) : null
}
/>>
This also renders the component as a jsx tag <TopMenuComponent />, which works for me where the other approach did not.
You can use useRouteMatch hook
const ParentComponent = props => {
const matched = useRouteMatch(['/', '/login'])
if (matched && matched.isExact) return null
return <ChildComponent {...props} />
}

Navigate in code with react-router-dom 4.0?

Looking at this video the react router seems easy to use, but I can't find how to navigate in my code since I want to link on clicking a div and not use <Link>.
I've search StackOverflow but haven't found any answers that work with 4.0. Trying to import browserHistory gives undefined (before and after installing 'react-router' in addition to 'react-router-dom') from this question:
import { browserHistory } from 'react-router';
console.log('browserHistory:', browserHistory);
I also saw somewhere that there is a 'context' you can get to, but this shows a value for 'match' but not 'context':
<Route path="/" render={({ match, context}) => {
console.log('match:', match);
console.log('context:', context);
Edit
In the dev tools I can see that "Router" has a history property, so when I add that I can get to it:
<Route path="/" render={({ match, context, history}) => {
Is there a way to get to this from outside a route? For example a navbar component that will navigate to other components, but is not inside a Route itself...
If I understand your question, this is how you make a link programaticaly.
class Test extends React.Component {
handleClick() {
console.log(this.context);
this.context.router.history.push('/some/path');
},
render() {
return (
<div onClick={handleClick}>
This is div.
</div>
)
}
}
Test.contextTypes = {
router: React.PropTypes.object.isRequired
}
ReactDOM.render(
<Test />,
document.getElementById("app")
);
Had to read into the docs more. The history object is only passed as a property using the component (or other) attributes on a Route. Apparently need to include the 'history' package and use createBrowserHistory and pass it to the Router, then specify the component in a Route. I think this should work fine since exact isn't specified...
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
ReactDOM.render( (
<Router history={ history }>
<Route path="/" component={ App } />
</Router>
),
document.getElementById('root')
);
Before I just had <App/> inside <Router> and didn't have access to those properties.
Why don't you just wrap your div in the link instead of trying to circumvent it and make your life easier?
<Link to="/" >
<div className="to-component">go to component</div>
</Link>

react router not updating browser's location bar

I met a weird problem here, I am using a simple router set up like:
<Provider store={store}>
<div>
<Router
location='history'
history={createHistory({queryKey: false})}
onUpdate={() => window.scrollTo(0, 0)}>
<Route path="/" component={Main}>
<Route path="datacenter/:name" component={App} />
<Route path="operation" component={OperationComponent} />
</Route>
</Router>
<DevTools />
</div>
</Provider>
and I have a nav list that manipulates the History:
<SelectableList subheader='OPERATIONS' valueLink={{value: this.props.location.pathname, requestChange: this.operationChanged}}>
<ListItem primaryText='Checks' value='/operation' onTouchTap={this.handleClose}/>
</SelectableList>
with action callback:
operationChanged = (evt, val) => {
this.props.history.push(val);
};
the problem is the navigation is working, when I click one of the list item, I am navigated to the path as expected, however, the selected path is not being updated to browser's address bar ...
Any ideas?
Thanks in advance
UPDATE *********
Turns out, it is an issue related to webpack-dev-server, while using this dev tool, the react-router failed to update browser's location bar ... did not figure out why though
Could be because on the Router you are setting:
location='history'
Try removing that line, also a JSBin or plnkr would be helpful so I could play around with it!
check out the docs here and follow the basic setup: Check out the bottom of the second example!