In my react application I have a component currentUserMixin which I am using for managing user sign in.
I want my LoginComponent(which shows the login form) to not be visible when a user is already logged in.
After I looked here How to restrict access to routes in react-router? I created this component in currentUserMixin:
NeedsUnAuthenticatedUser: {
statics: {
willTransitionTo: function(transition) {
if(this.authenticated()) {
transition.abort();
}
}
}
}
My issue is now that I don't know where to add this component.
Should I add it in the main app component like this:
export default class App extends Component {
mixins: [currentUserMixin.NeedsUnAuthenticatedUser]
and this:
<Route path="auth" component={AuthIndex}>
<Route path="login" component={LoginComponent} onEnter={willTransitionTo} />
<Route path="register" component={RegisterComponent} />
</Route>
Or should I put it in the actual login component like this:
export default class LoginComponent extends Component {
mixins: [currentUserMixin.NeedsUnAuthenticatedUser]
So where exactly should I put this mixin?
Mixins don't work with ES6 classes. If you want to use mixins, then you should use React.createClass(). When using ES6 classes normally you can use a high order component to achieve what you want:
function getDisplayName (WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
function needsUnAuthenticatedUser (Component) {
class WrapperComponent extends Component {
authenticated () {
//...
}
willTransitionTo (transition) {
if(this.authenticated()) {
transition.abort();
}
}
render () {
return <Component {...this.props} />
}
}
// This is just to display nice stacktraces / inspector name for the component
WrapperComponent.displayName = `NeedsUnAuthenticatedUser(${getDisplayName(Component)})`
return WrapperComponent
}
// Usage example:
class ProfilePage extends Component {
render () {
return <div>I should be accessible only if user is not logged in...</div>
}
}
export default needsUnAuthenticatedUser(ProfilePage)
// or const authedProfilePage = needsUnAuthenticatedUser(ProfilePage)
Check this out for a very nice explanation:
https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.wy6wkysw8
If you prefer, you can use instead the onEnter hook of your Route to redirect the user if already logged in, but also in that case you won't use a mixin, just a plain function:
function checkUserAlreadyLoggedIn (nextState, replace) {
// put some logic here to decide if use need to redirect
if (alreadyLoggedIn) {
replace(/your-redirect-url)
}
}
const routes = (<Route path="/" component={App}>
<Route path="/foo" onEnter={checkUserAlreadyLoggedIn} component={LoginForm} />
</Route>)
Related
I'm a beginner learning React and using React v 17.0.2, react-router-dom v 6.0.2. I'm following a course made for react-router-dom v4. I'm not able to get page navigation working if I try to navigate from a successful login to append a welcome message to the url. In v4 this is achieved by a {this.props.history.push("/welcome") method. I'm not able to something equivalent in V6. Specifically, I would like to know how to handle the loginClicked method.
Based on the helpful guidance from Himanshu Singh, I tried the following:
import { computeHeadingLevel } from '#testing-library/react'
import React, { Component } from 'react'
import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'
class TodoApp extends Component {
render() {
return (
<div className="TodoApp">
<Router>
<Routes>
<Route path="/" exact element={<LoginComponent />} />
<Route path="/enlite" element={<LoginComponent />} />
<Route path="/welcome" element={<WelcomeComponent />} />
</Routes>
</Router>
{/* <LoginComponent /> */}
</div>
)
}
}
class WelcomeComponent extends Component {
render() {
return <div>Welcome to Enlite</div>
}
}
class LoginComponent extends Component {
constructor(props) {
super(props)
this.state = {
username: 'testuser',
password: '',
hasLoginFailed: false,
showSuccessMessage: false
}
this.handleChange = this.handleChange.bind(this)
this.loginClicked = this.loginClicked.bind(this)
}
handleChange(event) {
this.setState(
{
[event.target.name]
: event.target.value
})
}
**loginClicked() {
if (this.state.username === 'testuser' &&
this.state.password === 'dummy') {
function HandlePageNav() {
let navigate = useNavigate()
navigate('/welcome')
}
**HandlePageNav();**
}
else {
this.setState({ showSuccessMessage: false })
this.setState({ hasLoginFailed: true })
}
}**
render() {
return (
<div>
{this.state.hasLoginFailed && <div>Invalid Credentials</div>}
{this.state.showSuccessMessage && <div>Welcome to Enlite</div>}
User Name: <input type="text" name="username" value={this.state.username} onChange={this.handleChange} />
Password: <input type="password" name="password" value={this.state.password} onChange={this.handleChange} />
<button onClick={this.loginClicked}>Login</button>
</div>
)
}
}
export default TodoApp
This gives the following error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
Basically calling hooks in class components is not supported. I also tried to completely do away with the function like this:
loginClicked() {
if (this.state.username === 'testuser' &&
this.state.password === 'dummy') {
let navigate = useNavigate()
navigate('/welcome')
}
else {
this.setState({ showSuccessMessage: false })
this.setState({ hasLoginFailed: true })
}
}
This gives a compile error:
Line 85:32: React Hook "useNavigate" cannot be called in a class
component. React Hooks must be called in a React function component or
a custom React Hook function react-hooks/rules-of-hooks Line 89:13:
'HandlePageNav' is not defined
no-undef
The above makes me wonder if I need to refactor my entire code into a function component or if there's a way to achieve this page navigation but keeping the class component. Other than that I would appreciate any help or insights on this problem. Thanks in advance.
UseNavigate Hook will not work here because hooks are meant to be used in functional components not class components.
What you can do for now is, since no proper doc is provided for class component
Try to use Functional Components : the most easiest way
Use a HOC component around the class component and pass history and other necessary props to it through that component.
Note: Here I tried second approach. You can follow this: https://codesandbox.io/s/snowy-moon-30br5?file=/src/App.js
I have these routes
<Route exact path={`/admin/caters/:id`} component={Cater} />
<Route exact path={'/admin/caters/create'} component={CreateCater} />
When I navigate to the first route I get a cater with a given ID. And the Cater component is rendered
When I navigate to the second route, the CreateCater component is rendered on the page, but I noticed that some redux actions that are used in the Cater component are being run. So both component are somehow being rendered - but I can't figure out why.
Here are the components:
Cater:
class Cater extends Component {
async componentDidMount() {
console.log('Cater component did mount')
const { match: { params: { id }}} = this.props
this.props.get(id)
}
render() {
const { cater } = this.props
if(!cater) {
return null
}
else {
return (
<div>
... component data ...
</div>
)
}
}
}
const mapStateToProps = (state, props) => {
const { match: { params: { id }}} = props
return {
cater: caterSelectors.get(state, id)
}
}
const mapDispatchToProps = (dispatch, props) => {
return {
get: (id) => dispatch(caterActions.get(id))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cater)
CreateCater:
export default class CreateCaterPage extends Component {
render() {
return (
<React.Fragment>
<Breadcrumbs />
<CaterForm />
</React.Fragment>
)
}
}
When I go to /admin/caters/create' I can see the console.log in the componenDidMount() lifecycle method inside the Cater component.
I cant figure out what I am doing wrong :(
/create matches /:id, so it makes sense that this route matches. I recommend forcing :id to look for numeric only:
<Route exact path={`/admin/caters/:id(\\d+)`} component={Cater} />
<Route exact path={'/admin/caters/create'} component={CreateCater} />
Likewise, you can follow #jabsatz's recommendation, use a switch, and have it match the first route that matches. In this case, you would need to ensure that the /admin/caters/create route is the first <Route /> element matched.
The problem is that :id is matching with create (so, it thinks "see cater with id create"). The way to solve this is to put the wildcard matching route last, and wrapping all the <Routes/> with a <Switch/>, so it only renders the first hit.
Check out the docs if you have any more questions: https://reacttraining.com/react-router/core/api/Switch
I have created a custom button component for my website's navbar. When the user clicks on a button, the component returns a Redirect, which takes the user to the page they selected.
export default class Button extends Component {
constructor(props){
super(props);
this.state = {redirect:false};
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.stopPropagation();
this.setState({redirect: true});
}
componentDidUpdate() {
if (this.state.redirect){
this.setState({redirect:false});
this.props.onRedirect();
}
}
render() {
if (this.state.redirect){
return <Redirect push to={this.props.dest}/>;
}
else {
return (
<li className="button" onClick={this._handleClick}>
<h5>{this.props.text}</h5>
</li>
);
}
}
}
Now, I'd like to add buttons that correspond to different sections of the same page. The simplest way I know of is to use hash links. One example of an address the button would redirect to is:
/home#description
However, React Router does not support doing this out of the box. I looked through a number of packages which add this functionality, such as react-router-hash-link and react-scrollchor. None of these however work with redirects, instead relying on Link or on custom components.
How do I go about adding this functionality to the buttons?
you could update window.location.href since it won't trigger a page refresh.
e.g.
window.location.href = '#your-anchor-tag';
One solution that I can think of is to use HOCs and hooks. The end result:
You'll get your app to scroll to the specified location...
without really needing to create custom buttons/links and...
without making much changes to your existing screens (Eg: HomeScreen)
Bonus: Users can copy, share & use URLs that will automatically scroll to the intended section
With assumption that the code below are pseudocode (they are based on my knowledge and not tested) and assuming there's a HomeScreen component, I would attempt adding <Route/>s to the <Switch/> inside the <Router/>.
<Switch>
<Route to='/home/:section' component={HomeScreen} />
<Route to='/home' component={HomeScreen} />
</Switch>
Then:
function withScrollToTarget(WrappedComponent) {
class WithScroll extends React.Component {
componentDidMount() {
const { match: { params: { section } } } = this.props
// Remember we had 2 <Route/>s, so if `section` is provided...
if (section) {
const scrollToTarget = document.getElementById(section)
// And just in case the item was removed or there was an ID mismatch
if (scrollToTarget) { scrollToTarget.scrollIntoView() }
}
}
render() { return <WrappedComponent {...this.props} /> }
}
return WithScroll
}
function useScrollToTarget(section) {
useEffect(() => {
if (section) {
const scrollToTarget = document.getElementById(section)
if (scrollToTarget) { scrollToTarget.scrollIntoView() }
}
}, [section])
}
Usage:
<nav>
<Link to='/home'>{'Home'}</Link>
<Link to='/home/description'>{'Description'}</Link>
</nav>
class HomeScreen extends React.Component { /* ... */ }
export default withScrollToTarget(HomeScreen)
// or
function HomeScreen() {
const { params: { section } } = useMatch() // from react-router-dom
useScrollTotarget(section)
return (
<div>
<h1 id='introduction'>Introduction</h1>
<h1 id='description'>Description</h1>
</div>
)
}
TLDR:
The route for '/home/:section' must be on top of '/home'. If the opposite, every time when <Switch/> compares the current URL against to, it will evaluate to true upon reaching '/home' and never reach '/home/:section'
scrollIntoView() is a legit function
If this works for you, you should look up on how to forward refs and hoisting statics in HOCs too
Who said React Router doesn't support this out of the box! You don't need those packages. You can redirect a hash i'll give you an example using the React-Router Route.
<Route
exact
path="/signup"
render={props => {
if (props.location.hash === "#foo")
return <Redirect push to="signup#bar"
return <Signup />
}}
/>
Now your version may not have supported this now that I think about it, but let me know if this helps :)
Happy coding!
React-hash-link should work for your redirect use case.
You can add <HashLinkObserver /> to your component tree and it will listen for hash links and scroll accordingly rather than relying on Link or custom components.
I think you should use the react-router-dom.
yarn add react-router-dom
Now update Custom Button Component like this
import React from 'react';
import { withRouter } from "react-router-dom";
class Button extends Component {
constructor(props){
super(props);
this.state = {redirect:false};
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.stopPropagation();
this.setState({redirect: true});
}
componentDidUpdate() {
if (this.state.redirect){
this.setState({redirect:false});
//this.props.onRedirect();
this.props.history.push('new uri');
}
}
render() {
if (this.state.redirect){
return <Redirect push to={this.props.dest}/>;
}
else {
return (
<li className="button" onClick={this._handleClick}>
<h5>{this.props.text}</h5>
</li>
);
}
}
}
export default withRouter(Button);
I was trying to solve a similar but slightly different issue, I want to deprecate an old hash route in favor of a new one. The posts here helped me arrive to my eventual solution:
<Route
exact
path={'/thing/:id'}
render={({
match: {
params: { id },
},
}) => (
<Redirect
push
to={`/newThing/${id}`}
/>
)}
/>
I was facing the same issue, I have created HOC to handle hash redirection, you can follow the below steps to achieve a hash redirection
create HOC and add below code to it
fileName : hashComponent
import React, { useEffect } from 'react';
export default function hashComponent(WrappedComponent) {
return function () {
const { pathname, hash }=window.location;
useEffect(() => {
if(hash)
window.location.href=`${pathname}${hash}`;
}, [hash])
return <WrappedComponent />
}
}
import your HOC in the component to which you want to handle hash URL
Then add below line of code while exporting your component
export default hashComponent(YourComponentName)
I'm new to react so this is something I don't know. In the app that I
'm working with it has a main component where other components are loaded.
Like this,
render() {
return (
<div className="index">
<HeaderComponent />
<MainHeroComponent />
<AboutComponent />
</div>
);
}
And I want when someone clicks a link in HeaderComponent to show the about component. And hide the MainHeroComponent. How can I do such communication between components in React? Is it possibe?
Thanks
Use React-Router and create routes for this scenario instead of direct communication between components. Sample app structure using react-router
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<HeaderComponent />
</div>
)
}
})
render((
<Router>
<Route path="/" component={App}>
<Route path="hero" component={MainHeroComponent} />
<Route path="about" component={AboutComponent} />
</Route>
</Router>
), document.body)
For more details on router refer: https://github.com/reactjs/react-router/blob/master/docs/guides/RouteConfiguration.md
Aditya's answer is probably a better solution, but if you really want to it your way, you can use state and callbacks.
class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
showHero: true
};
this.toggleShowHero = this.toggleShowHero.bind(this);
}
toggleShowHero() {
this.setState({
showHero: !this.state.showHero
});
}
render() {
return (
<div className="index">
<HeaderComponent onClick={toggleShowHero}/>
{
this.state.showHero ?
<MainHeroComponent /> :
<AboutComponent />
}
</div>
);
}
There are various ways you can achieve this, including React-routers and Redux, but since you're new to React, it'll be good if you get familiar with the basics first. For a start, you have to change the state of the main component to decide which child component to render.
In the main component code snippet you posted, initialize a state in the constructor as follows:
/* in the main component */
constructor(props) {
super(props);
this.state = {
showAbout: true
};
}
Then modify the render function as follows, to pass a reference to your main component, down to your header component:
/* in the main component */
<HeaderComponent mainComponent={this}/>
Then, in HeaderComponent, attach a click event handler to the link on which you want to perform the operation.
/* in HeaderComponent */
<a href="#" ....... onClick={this.showAbout.bind(this)}>Show About</a>
In the same component, define the showAbout function as follows:
/* in HeaderComponent */
showAbout () {
let mainComponent = this.props.mainComponent;
mainComponent.setState({
showAbout: true
)};
}
Finally, back in the render function of the main component:
/* in the main component */
render () {
let mainHeroComponent, aboutComponent;
if (this.state.showAbout) {
aboutComponent = (
<AboutComponent/>
);
} else {
mainHeroComponent = (
<MainHeroComponent/>
);
}
return (
<div className="index">
<HeaderComponent mainComponent={this}/>
{mainHeroComponent}
{aboutComponent}
</div>
);
}
And you're done! Basically, a component gets re-rendered every time its state is changed. So each time you click on the link, the main component's state is changed with a new value of showAbout. This will cause the main component to re-render itself, and, based on the value of showAbout, it will decide whether to render MainHeroComponent or AboutComponent.
But you should make sure you have a similar logic to display MainHeroComponent as well, instead of AboutComponent, just to switch the views.
My app is ES6 React application with react-router. I want to redirect user to a different page after a small delay. Here is my React component:
import React from 'react'
import { Navigation } from 'react-router'
export default class Component extends React.Component {
render () {
return (
<div>Component content</div>
)
}
componentDidMount () {
setTimeout(() => {
// TODO: redirect to homepage
console.log('redirecting...');
this.context.router.transitionTo('homepage');
}, 1000);
}
}
Component.contextTypes = {
router: React.PropTypes.func.isRequired
}
And react-router routing table:
render(
<Router>
<Route path='/' component={ App }>
<IndexRoute component={ Component } />
</Route>
</Router>
, document.getElementById('app-container'));
The issue is that 'router' property is not passed into the component. Chrome console's content is:
Warning: Failed Context Types: Required context `router` was not specified in `Component`. Check the render method of `RoutingContext`.
redirecting...
Uncaught TypeError: Cannot read property 'transitionTo' of undefined
React version is 0.14.2, react-router version is 1.0.0-rc4
Where do I make mistake?
Im not a react-router expert by any means, but I had the same issue earlier today. I am using React 0.14.2 and React-Router 1.0 (this just came out in the last couple of days, if not more recently). While debugging I noticed that the props on the React component includes history (the new style of navigation - https://github.com/rackt/react-router/blob/master/docs/guides/basics/Histories.md)
I am also using TypeScript, but my code looks like the following:
import React = require('react');
import Header = require('./common/header.tsx');
var ReactRouter = require('react-router');
interface Props extends React.Props<Home> {
history: any
}
class Home extends React.Component<Props, {}> {
render(): JSX.Element {
return (
<div>
<Header.Header MenuItems={[]} />
<div className="jumbotron">
<h1>Utility</h1>
<p>Click on one of the options below to get started...</p>
{<a className="btn btn-lg" onClick={() => this.props.history.pushState(null, '/remoteaccess') }>Remote Access</a>}
{<a className="btn btn-lg" onClick={() => this.props.history.pushState(null, '/bridge') }>Bridge</a>}
</div>
</div>
);
}
}
module.exports = Home;
I'm inclined to say that this.context.router doesn't exist anymore. I've run into the same problem and it looks like we're supposed to implement these features using this.context.history as well as this.context.location. If I get something working, I'll try updating this response.
See the 1.0.0 upgrade guide and use this.props.history with pushState or replaceState. context is used for other components (not route components).
From the upgrade guide :
// v1.0
// if you are a route component...
<Route component={Assignment} />
var Assignment = React.createClass({
foo () {
this.props.location // contains path information
this.props.params // contains params
this.props.history.isActive('/pathToAssignment')
}
})