How can I disable a <Link> in react-router, if its URL already active? E.g. if my URL wouldn't change on a click on <Link> I want to prevent clicking at all or render a <span> instead of a <Link>.
The only solution which comes to my mind is using activeClassName (or activeStyle) and setting pointer-events: none;, but I'd rather like to use a solution which works in IE9 and IE10.
You can use CSS's pointer-events attribute. This will work with most of the browsers. For example your JS code:
class Foo extends React.Component {
render() {
return (
<Link to='/bar' className='disabled-link'>Bar</Link>
);
}
}
and CSS:
.disabled-link {
pointer-events: none;
}
Links:
pointer-events CSS property
How to disable HTML links
The How to disable HTML links answer attached suggested using both disabled and pointer-events: none for maximum browser-support.
a[disabled] {
pointer-events: none;
}
Link to source: How to disable Link
This works for me:
<Link to={isActive ? '/link-to-route' : '#'} />
I'm not going to ask why you would want this behavior, but I guess you can wrap <Link /> in your own custom link component.
<MyLink to="/foo/bar" linktext="Maybe a link maybe a span" route={this.props.route} />
class MyLink extends Component {
render () {
if(this.props.route === this.props.to){
return <span>{this.props.linktext}</span>
}
return <Link to={this.props.to}>{this.props.linktext}</Link>
}
}
(ES6, but you probably get the general idea...)
Another possibility is to disable the click event if clicking already on the same path. Here is a solution that works with react-router v4.
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
class SafeLink extends Component {
onClick(event){
if(this.props.to === this.props.history.location.pathname){
event.preventDefault();
}
// Ensure that if we passed another onClick method as props, it will be called too
if(this.props.onClick){
this.props.onClick();
}
}
render() {
const { children, onClick, ...other } = this.props;
return <Link onClick={this.onClick.bind(this)} {...other}>{children}</Link>
}
}
export default withRouter(SafeLink);
You can then use your link as (any extra props from Link would work):
<SafeLink className="some_class" to="/some_route">Link text</SafeLink>
All the goodness of React Router NavLink with the disable ability.
import React from "react"; // v16.3.2
import { withRouter, NavLink } from "react-router-dom"; // v4.2.2
export const Link = withRouter(function Link(props) {
const { children, history, to, staticContext, ...rest } = props;
return <>
{history.location.pathname === to ?
<span>{children}</span>
:
<NavLink {...{to, ...rest}}>{children}</NavLink>
}
</>
});
React Router's Route component has three different ways to render content based on the current route. While component is most typically used to show a component only during a match, the children component takes in a ({match}) => {return <stuff/>} callback that can render things cased on match even when the routes don't match.
I've created a NavLink class that replaces a Link with a span and adds a class when its to route is active.
class NavLink extends Component {
render() {
var { className, activeClassName, to, exact, ...rest } = this.props;
return(
<Route
path={to}
exact={exact}
children={({ match }) => {
if (match) {
return <span className={className + " " + activeClassName}>{this.props.children}</span>;
} else {
return <Link className={className} to={to} {...rest}/>;
}
}}
/>
);
}
}
Then create a navlink like so
<NavLink to="/dashboard" className="navlink" activeClassName="active">
React Router's NavLink does something similar, but that still allows the user to click into the link and push history.
const [isActive, setIsActive] = useState(true);
<Link to={isActive ? '/link-to-route' : null} />
you can try this, this worked for me.
Create a slim custom component like this below, you can also apply styling & css if you want as well maybe play with the opacity and pointer events none etc... or you can set the "to" to null when disabled from props
type Props = { disabled?: boolean;} & LinkProps;
const CustomLinkReactRouter = (props: Props) => {
const { disabled, ...standardProps } = props;
return <Link {...standardProps} onClick={e => disabled && e.preventDefault()}/>
}
export default CustomLinkReactRouter;
Based on nbeuchat's answer and component - I've created an own improved version of component that overrides react router's Link component for my project.
In my case I had to allow passing an object to to prop (as native react-router-dom link does), also I've added a checking of search query and hash along with the pathname
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Link as ReactLink } from 'react-router-dom';
import { withRouter } from "react-router";
const propTypes = {
to: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),
location: PropTypes.object,
children: PropTypes.node,
onClick: PropTypes.func,
disabled: PropTypes.bool,
staticContext: PropTypes.object
};
class Link extends Component {
handleClick = (event) => {
if (this.props.disabled) {
event.preventDefault();
}
if (typeof this.props.to === 'object') {
let {
pathname,
search = '',
hash = ''
} = this.props.to;
let { location } = this.props;
// Prepend with ? to match props.location.search
if (search[0] !== '?') {
search = '?' + search;
}
if (
pathname === location.pathname
&& search === location.search
&& hash === location.hash
) {
event.preventDefault();
}
} else {
let { to, location } = this.props;
if (to === location.pathname + location.search + location.hash) {
event.preventDefault();
}
}
// Ensure that if we passed another onClick method as props, it will be called too
if (this.props.onClick) {
this.props.onClick(event);
}
};
render() {
let { onClick, children, staticContext, ...restProps } = this.props;
return (
<ReactLink
onClick={ this.handleClick }
{ ...restProps }
>
{ children }
</ReactLink>
);
}
}
Link.propTypes = propTypes;
export default withRouter(Link);
Another option to solve this problem would be to use a ConditionalWrapper component which renders the <Link> tag based on a condition.
This is the ConditionalWrapper component which I used based on this blog here
https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2:
const ConditionalWrapper = ({ condition, wrapper, children }) =>
condition ? wrapper(children) : children;
export default ConditionalWrapper
This is how we have used it:
const SearchButton = () => {
const {
searchData,
} = useContext(SearchContext)
const isValid = () => searchData?.search.length > 2
return (<ConditionalWrapper condition={isValid()}
wrapper={children => <Link href={buildUrl(searchData)}>{children}</Link>}>
<a
className={`ml-auto bg-${isValid()
? 'primary'
: 'secondary'} text-white font-filosofia italic text-lg md:text-2xl px-4 md:px-8 pb-1.5`}>{t(
'search')}</a>
</ConditionalWrapper>
)
}
This solution does not render the Link element and avoids also code duplication.
If it fits your design, put a div on top of it, and manipulate the z-index.
Related
How can I use react-router, and have a link navigate to a particular place on a particular page? (e.g. /home-page#section-three)
Details:
I am using react-router in my React app.
I have a site-wide navbar that needs to link to a particular parts of a page, like /home-page#section-three.
So even if you are on say /blog, clicking this link will still load the home page, with section-three scrolled into view. This is exactly how a standard <a href="/home-page#section-three> would work.
Note: The creators of react-router have not given an explicit answer. They say it is in progress, and in the mean time use other people's answers. I'll do my best to keep this question updated with progress & possible solutions until a dominant one emerges.
Research:
How to use normal anchor links with react-router
This question is from 2015 (so 10 years ago in react time). The most upvoted answer says to use HistoryLocation instead of HashLocation. Basically that means store the location in the window history, instead of in the hash fragment.
Bad news is... even using HistoryLocation (what most tutorials and docs say to do in 2016), anchor tags still don't work.
https://github.com/ReactTraining/react-router/issues/394
A thread on ReactTraining about how use anchor links with react-router. This is no confirmed answer. Be careful since most proposed answers are out of date (e.g. using the "hash" prop in <Link>)
React Router Hash Link worked for me and is easy to install and implement:
$ npm install --save react-router-hash-link
In your component.js import it as Link:
import { HashLink as Link } from 'react-router-hash-link';
And instead of using an anchor <a>, use <Link> :
<Link to="home-page#section-three">Section three</Link>
Note: I used HashRouter instead of Router:
This solution works with react-router v5
import React, { useEffect } from 'react'
import { Route, Switch, useLocation } from 'react-router-dom'
export default function App() {
const { pathname, hash, key } = useLocation();
useEffect(() => {
// if not a hash link, scroll to top
if (hash === '') {
window.scrollTo(0, 0);
}
// else scroll to id
else {
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView();
}
}, 0);
}
}, [pathname, hash, key]); // do this on route change
return (
<Switch>
<Route exact path="/" component={Home} />
.
.
</Switch>
)
}
In the component
<Link to="/#home"> Home </Link>
Here is one solution I have found (October 2016). It is is cross-browser compatible (tested in Internet Explorer, Firefox, Chrome, mobile Safari, and Safari).
You can provide an onUpdate property to your Router. This is called any time a route updates. This solution uses the onUpdate property to check if there is a DOM element that matches the hash, and then scrolls to it after the route transition is complete.
You must be using browserHistory and not hashHistory.
The answer is by "Rafrax" in Hash links #394.
Add this code to the place where you define <Router>:
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
const routes = (
// your routes
);
function hashLinkScroll() {
const { hash } = window.location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) element.scrollIntoView();
}, 0);
}
}
render(
<Router
history={browserHistory}
routes={routes}
onUpdate={hashLinkScroll}
/>,
document.getElementById('root')
)
If you are feeling lazy and don't want to copy that code, you can use Anchorate which just defines that function for you. https://github.com/adjohnson916/anchorate
Here's a simple solution that doesn't require any subscriptions nor third-party packages. It should work with react-router#3 and above and react-router-dom.
Working example: https://fglet.codesandbox.io/
Source (unfortunately, it doesn't currently work within the editor):
#ScrollHandler Hook Example
import { useEffect } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
const ScrollHandler = ({ location, children }) => {
useEffect(
() => {
const element = document.getElementById(location.hash.replace("#", ""));
setTimeout(() => {
window.scrollTo({
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
});
}, 100);
}, [location]);
);
return children;
};
ScrollHandler.propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.shape({
hash: PropTypes.string,
}).isRequired
};
export default withRouter(ScrollHandler);
#ScrollHandler Class Example
import { PureComponent } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
class ScrollHandler extends PureComponent {
componentDidMount = () => this.handleScroll();
componentDidUpdate = prevProps => {
const { location: { pathname, hash } } = this.props;
if (
pathname !== prevProps.location.pathname ||
hash !== prevProps.location.hash
) {
this.handleScroll();
}
};
handleScroll = () => {
const { location: { hash } } = this.props;
const element = document.getElementById(hash.replace("#", ""));
setTimeout(() => {
window.scrollTo({
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
});
}, 100);
};
render = () => this.props.children;
};
ScrollHandler.propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.shape({
hash: PropTypes.string,
pathname: PropTypes.string,
})
};
export default withRouter(ScrollHandler);
Just avoid using react-router for local scrolling:
document.getElementById('myElementSomewhere').scrollIntoView()
The problem with Don P's answer is sometimes the element with the id is still been rendered or loaded if that section depends on some async action. The following function will try to find the element by id and navigate to it and retry every 100 ms until it reaches a maximum of 50 retries:
scrollToLocation = () => {
const { hash } = window.location;
if (hash !== '') {
let retries = 0;
const id = hash.replace('#', '');
const scroll = () => {
retries += 0;
if (retries > 50) return;
const element = document.getElementById(id);
if (element) {
setTimeout(() => element.scrollIntoView(), 0);
} else {
setTimeout(scroll, 100);
}
};
scroll();
}
}
I adapted Don P's solution (see above) to react-router 4 (Jan 2019) because there is no onUpdate prop on <Router> any more.
import React from 'react';
import * as ReactDOM from 'react-dom';
import { Router, Route } from 'react-router';
import { createBrowserHistory } from 'history';
const browserHistory = createBrowserHistory();
browserHistory.listen(location => {
const { hash } = location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(
() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView();
}
},
0
);
}
});
ReactDOM.render(
<Router history={browserHistory}>
// insert your routes here...
/>,
document.getElementById('root')
)
<Link to='/homepage#faq-1'>Question 1</Link>
useEffect(() => {
const hash = props.history.location.hash
if (hash && document.getElementById(hash.substr(1))) {
// Check if there is a hash and if an element with that id exists
document.getElementById(hash.substr(1)).scrollIntoView({behavior: "smooth"})
}
}, [props.history.location.hash]) // Fires when component mounts and every time hash changes
For simple in-page navigation you could add something like this, though it doesn't handle initializing the page -
// handle back/fwd buttons
function hashHandler() {
const id = window.location.hash.slice(1) // remove leading '#'
const el = document.getElementById(id)
if (el) {
el.scrollIntoView()
}
}
window.addEventListener('hashchange', hashHandler, false)
An alternative: react-scrollchor https://www.npmjs.com/package/react-scrollchor
react-scrollchor: A React component for scroll to #hash links with smooth animations. Scrollchor is a mix of Scroll and Anchor
Note: It doesn't use react-router
Create A scrollHandle component
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export const ScrollHandler = ({ children}) => {
const { pathname, hash } = useLocation()
const handleScroll = () => {
const element = document.getElementById(hash.replace("#", ""));
setTimeout(() => {
window.scrollTo({
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
});
}, 100);
};
useEffect(() => {
handleScroll()
}, [pathname, hash])
return children
}
Import ScrollHandler component directly into your app.js file
or you can create a higher order component withScrollHandler and export your app as withScrollHandler(App)
And in links <Link to='/page#section'>Section</Link> or <Link to='#section'>Section</Link>
And add id="section" in your section component
I know it's old but in my latest react-router-dom#6.4.4, this simple attribute reloadDocument is working:
div>
<Link to="#result" reloadDocument>GO TO ⬇ (Navigate to Same Page) </Link>
</div>
<div id='result'>CLICK 'GO TO' ABOVE TO REACH HERE</div>
Can someone help me to figure out what can be the significance of passing the Link tag inside the NavLink component like this :
<NavLink tag={Link} to="/components/" activeClassName="active">Components</NavLink>
The code for NavLink (reactstrap component) is given below :
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { mapToCssModules, tagPropType } from './utils';
const propTypes = {
tag: tagPropType,
innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]),
disabled: PropTypes.bool,
active: PropTypes.bool,
className: PropTypes.string,
cssModule: PropTypes.object,
onClick: PropTypes.func,
href: PropTypes.any,
};
const defaultProps = {
tag: 'a',
};
class NavLink extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
if (this.props.disabled) {
e.preventDefault();
return;
}
if (this.props.href === '#') {
e.preventDefault();
}
if (this.props.onClick) {
this.props.onClick(e);
}
}
render() {
let {
className,
cssModule,
active,
tag: Tag,
innerRef,
...attributes
} = this.props;
const classes = mapToCssModules(classNames(
className,
'nav-link',
{
disabled: attributes.disabled,
active: active
}
), cssModule);
return (
<Tag {...attributes} ref={innerRef} onClick={this.onClick} className={classes} />
);
}
}
NavLink.propTypes = propTypes;
NavLink.defaultProps = defaultProps;
export default NavLink;
Here you can see that the NavLink returns a component wrapped inside the tag we passed as props. The basic function of Link (react-router component) i.e routing the components is not accomplished here. So passing it as a prop for NavLink is confusing me.
I believe it's a how they provide re-usability over the Link component coming from the react-router or maybe any other Link component you want to use! what we basically have is:
// react-router/Link
<Link to="/about">About</Link>
What they have in NavLink:
<Tag {...attributes} ref={innerRef} onClick={this.onClick} className={classes} />
Where {...attributes} will be any other property other than:
className, cssModule, active, tag, innerRef, because they are destructed from props.
So, The reason they did that.
They needed/provided onClick property for the Link component.
They have there own standard to styling stuff className={classes}
And, one of the most important things in React is it's Component's Re-usability, meaning, DRY principle is applied in this matter, because, Imagine you don't have the NavLink Component and you want to add a onClick prop for the Link component whenever it's needed, then you'll have to carry this around wherever you go:
onClick(e) {
if (this.props.disabled) {
e.preventDefault();
return;
}
if (this.props.href === '#') {
e.preventDefault();
}
if (this.props.onClick) {
this.props.onClick(e);
}
}
Shortening that: it's all for the sake of DRY Principle
I am pretty much familiar with the React.js but new to Gatsby.
I want to detect the previous page URL in Gatsby?
You can pass down state using the Link component:
import React from 'react';
import { Link } from 'gatsby';
const PrevPage = () => (
<div>
<Link
to={`/nextpage`}
state={{ prevPath: location.pathname }}
>
Next Page
</Link>
</div>
)
const NextPage = (props) => (
<div>
<p>previous path is: {props.location.state.prevPath}</p>
</div>
);
Then you have access to prevPath from this.props.location.state in the next page.
Full credit to #soroushchehresa's answer — this answer is just extras built upon it.
Gatsby will throw error during production build, since location is not available during server-side rendering. You could get around it by checking for window object first:
class Page extends React.Component {
state = {
currentUrl: '',
}
componentDidMount() {
if (typeof window == 'undefined') return
this.setState({ currentUrl: window.location.href })
}
render() {
return (
<Link to="..." state={{ prevUrl: this.state.currentUrl }}>
)
}
}
But this requires us to implement this on every page, which is tedious. Gatsby has already set up #reach/router for server-side rendering, so we can hook into its location props. Only router components get that props, but we can use #reach/router's Location component to pass it to other components.
With that, we can write a custom Link component that always pass previous url in its state:
// ./src/components/link-with-prev-url.js
import React from 'react'
import { Location } from '#reach/router'
import { Link } from 'gatsby'
const LinkWithPrevUrl = ({ children, state, ...rest }) => (
<Location>
{({ location }) => (
//make sure user's state is not overwritten
<Link {...rest} state={{ prevUrl: location.href, ...state}}>
{ children }
</Link>
)}
</Location>
)
export { LinkWithPrevUrl as Link }
Then we can import our custom Link component instead of Gatsby's Link:
- import { Link } from 'gatsby'
+ import { Link } from './link-with-prev-url'
Now each Gatsby page component will get this previous url props:
const SomePage = ({ location }) => (
<div>previous path is {location.state.prevUrl}</div>
);
You might also consider creating a container that store state for the client side & use the wrapRootElement or wrapPageElement in both gatsby-ssr.js and gatsby-browser.js.
These answers are partially correct. If you set state using link api then the state persists in browser history.
So if you go from Page1 to Page2 then the eg the state.prevUrl will correctly be set to Page1
But if the you go to Page3 from Page2 and then do a browser back then the state.prevUrl will still be Page1 which is false.
Best way I found to deal with this is to add something like this on the gatsby-browser.js
export const onRouteUpdate = ({ location, prevLocation }) => {
if (location && location.state)
location.state.referrer = prevLocation ? prevLocation.pathname : null
}
this way you will have always the previous url available on location.
I resolved my problem with the below piece of code. Here is the ref link https://github.com/gatsbyjs/gatsby/issues/10410
// gatsby-browser.js
exports.onRouteUpdate = () => {
window.locations = window.locations || [document.referrer]
locations.push(window.location.href)
window.previousPath = locations[locations.length - 2]
}
Now you can get previousPath can be accessed from anywhere.
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 want to change my root query parameter based on the this.state.eventid which is a child component, but I have no clue how to get props to relay root container. I started based on relay-starter-kit.
I have React component that has a dropdown menu, and onSelect it setStates for eventId
renderAttend() {
if (this.props.groups != null && this.state.success != true) {
var events = this.props.events.map(function(event){
var boundSelect = () => {this.setState({eventid:event.nodes[0].id})}
return <MenuItem style={{fontSize:20}}eventKey={event.nodes[0].id} onSelect={boundSelect.bind(this)}>{event.nodes[0].properties.summary} / {event.nodes[0].properties.start}</MenuItem>
},this)
var teams = this.props.groups.map(function(team){
var boundSelect = () => {this.setState({teamid:team.nodes[0].id})}
return <MenuItem style={{fontSize:20}}eventKey={team.nodes[0].id} onSelect={boundSelect.bind(this)}>{team.nodes[0].properties.name}</MenuItem>
},this)
return (
<div>
<ButtonGroup>
<DropdownButton style={{padding:"15px",fontSize:20}}title="Events" id="bg-vertical-dropdown-2">
{events}
</DropdownButton>
<DropdownButton style={{padding:"15px",fontSize:20,marginLeft:"5px"}} title="Groups" id="bg-vertical-dropdown-2">
{teams}
</DropdownButton>
</ButtonGroup>
</div>
)
}
}
I want to use this state to somehow change my root query...
my approute...
import Relay from 'react-relay';
export default class extends Relay.Route {
static paramDefinitions = {
eventId: {required: false}
};
static queries = {
Event : () => Relay.QL`query{eventState(eventId:$eventId)}`,
};
static routeName = 'AppHomeRoute';
}
and my app.js
import 'babel-polyfill';
import App from './components/App';
import AppHomeRoute from './routes/AppHomeRoute';
import React from 'react';
import ReactDOM from 'react-dom';
import Relay from 'react-relay';
ReactDOM.render(
<Relay.RootContainer
Component={App}
route= {new AppHomeRoute}
renderLoading={function() {
return <div style= {{display:"flex",justifyContent:"center",marginTop:"55px"}}> <h1>Loading...</h1></div>;
}}
renderFailure={function(error, retry) {
return (
<div>
<h1>Click Refresh</h1>
</div>
);
}}
/>,
document.getElementById('root')
);
Now I want to this.state.eventid from the react component to update my root query, but I have no idea how to pass data from child component to react root.container. I do not want to use react-router for this :)
p.s. this.props.events were passed to me by an ajax call so they are not saved in relay/graphql data.
For such a case, the better thing to do is to wrap your root query into a story like
{
store {
events(eventId:$eventId)
}
}
So in the root query you only have
export default class extends Route {
static queries = {
app:() => Relay.QL`query { store }`
};
static routeName = "AppRoute";
}
And in the page you create a fragemnt like
let RelayApp = createContainer(SomeComponent, {
initialVariables: {
eventId: null
},
fragments: {
app: () => Relay.QL `
fragment on Store {
id
events(eventId: $eventId) {
pageInfo {
hasNextPage
}
edges {
cursor
node {
name
...
}
}
}
}
`,
},
});
export
default RelayApp;
For the child component, you set the eventId and onChange event handler as props from parent component. And in the parent componet you implement the event handler and call this.props.setVariables({eventId: someVal}) like
// Child Component
export default class Menu extends Component {
render() {
return(
<ul>
<li onClick={() => this.props.selectItem(val)}>{val}</li>
...
</ul>
)
}
}
// Parent Component
class Main extends Component {
_selectItem = (val) => {
this.props.relay.setVariables({eventId: val});
}
render() {
return(
<div>
<Menu selectItem={() => this._selectItem}/>
</div>
)
}
}
let RelayApp = ...
export default Main
Hope this will help.
There is no easy way to solve this. Either use react-router-relay or nest your query like this and use this.props.relay.setVariables()
viewer {
eventState(eventid:$eventid) {
data
}
}