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)
Related
I am current building a react app with django, I am trying to navigate from the HomePage to the DataPage with corrsponding id. However, it return Page not found error. I am using react-router-dom v6.
Using the URLconf defined in robot.urls, Django tried these URL patterns, in this order:
admin/
api/
api-auth/
homepage
homepage/data
The current path, homepage/data/54, didn’t match any of these.
Here is my App.js
export default class App extends Component {
constructor(props) {
super(props);
}
renderHomePage() {
return (
<HomePage />
);
}
render() {
return (
<BrowserRouter>
<Routes>
<Route exact path='homepage/' element={this.renderHomePage()} />
<Route path='homepage/data/:id' element={<DataPage />} />
</Routes>
</BrowserRouter>
)
}
}
const appDiv = document.getElementById("app");
render(<App />, appDiv);
And I want to navigate to the DataPage below:
const EmtpyGrid = theme => ({
Grid: { ... }
});
function DataPage(props) {
const { classes } = props;
const { id } = useParams();
return (
<div>
... some material ui components ...
<div/>
)
};
DataPage.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(EmtpyGrid)(DataPage);
I was thinking whether I need configure my url.py in frontend as well, and I need to define a designated value for {id} returned from the materialui component first. Perhaps I need a button or <Link><Link/> for the navigation instead of just simply typing in the url? Still, no luck after many attempts. I am so confused right now.
If you know what is wrong with my code, please feel free to leave a comment. Thanks
After many tries and checking documents, I don't really need to configure my urls.py. I only things that I am missing is to put a parameter in my naviagate() from onRowClick={((rowData, event) => {navigate('data/');})} to onRowClick={((rowData, event) => {let id = event.sample_ID; navigate('data/' + id)})}; I was thinking the problem too complicated.
Thanks you guys for sharing!
I'm programming a react server webpage, trying to redirect from index.js (i.e: localhost:3000) to Login page: (localhost:3000/login), and from login to index (in case of failed login). What do I need to write in index.js and login.js?
This is for a react based app, using also redux framework. I've tried a few ways including setting up a BrowserRouter etc. All won't really do the redirecting.
My current code is this:
in index.js:
class Index extends Component {
static getInitialProps({store, isServer, pathname, query}) {
}
render() {
console.log(this.props);
//const hist = createMemoryHistory();
if (!this.props.isLoggedIn){
return(<Switch><Route exact path = "/login"/></Switch>)
}
else{...}
in login.js:
render() {
console.log(this.props);
if (fire.auth().currentUser != null) {
var db = fire.firestore();
db.collection("users").doc(fire.auth().currentUser.uid).get().then((doc) => {
this.props.dispatch({ type: 'LOGIN', user: doc.data() });
})
}
const { isLoggedIn } = this.props
console.log(isLoggedIn)
if (isLoggedIn) return <Redirect to='/' />
I except the root to redirect to login if no session is on, and login to redirect to root once there is a successful login.
I am currently getting "You should not use <Switch> outside a <Router>" at index (I have tried to wrap with BrowserRouter, ServerRouter, Router. the first says it needs DOM history. adding history does not change error. two others do not error but are blank display on browser.)
and "ReferenceError: Redirect is not defined" at login.
Any help will be appreciated.
you can use a HOC (Higher-Order Components)
something like this
export default ChildComponent => {
class ComposedComponent extends Component {
componentWillMount() {
this.shouldNavigateAway();
}
componentWillUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.authenticated) {
this.props.history.push('/')
}
}
render() {
return <ChildComponent {...this.props} />
}
}
}
As of now you're trying to return a route declaration wrapped in a Switch component. If you want to redirect the user to the /login page if hes not logged in, you need the route to be declared higher up in the component hierarchy, and then you would be able to return the <Redirect /> component. Either way, I would suggest you check out the react router documentation to see how they do authentication.
https://reacttraining.com/react-router/web/example/auth-workflow
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
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>)
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.