React web navigation based on anchors - reactjs

I'm trying to setup a single page react web application. My application only has one page with all the components places one after the other, so, the common react-router solution that lets you render only some part of the application doesn't work for me.
The intention is to have only one page (as large as it becomes) and the user can navigate between sections on it through a menu (or similar). Also, the site should recognize when someone types the URL like http://url.com/#section4, and scroll automatically to the requested section, marking the selected section on the menu as active.
Also, like a nice to have, it would be awesome if the URL can change depending on page scroll.
How can I achieve this?

I found this library that works in conjunction with the React Router's BrowserRouter, and allows to have a based anchor navigation, and also customize the scroll function were you can put offset or anything you wan to. Something like this:
<NavHashLink to="#anchor1" scroll={el => this.scroll(el)}>Anchor1</NavHashLink>
scroll (el) {
const offset = document.getElementById('element').offsetHeight
window.scrollTo({ top: el.offsetTop - offset, left: 0, behavior: 'smooth' })
}

You should either use HashRouter from react-router-dom package, or you can use a NavLink from the same package like this:
<NavLink>
to="/foo/#bar"
isActive={()=> window.location.pathname + window.location.hash === "/foo/#bar"}
</NavLink>

Related

Gatsby's `navigate` function to navigate to the same page?

Is it possible to use Gatsby's navigate function to navigate to the same page?
For instance, I built a search component that lives on the navigation bar, therefor accessible on any page. Once users perform search, they get routed to the Search Results page via navigate(/search-results).
If they are on that page, and want to perform the search again, that navigate function does not work, hence no results display.
I'm aware I could do a window.location.reload() but wanted to see if there are other ways.
Gatsby's navigation (Link component or navigate function) doesn't support parameters per se as you want to.
From Gatsby's docs:
Neither <Link> nor navigate can be used for in-route navigation with a
hash or query parameter. If you need this behavior, you should either
use an anchor tag or import the #reach/router package—which Gatsby
already depends upon—to make use of its navigate function, like so:
So, that said, you have two approaches:
Importing the navigation from #reach/router:
import { navigate } from '#reach/router';
onClick = () => {
navigate('?foo=bar');
}
Using window.location as you pointed
Of course, the #reach/router will give you a smoother workaround.

React Bootstrap Nav.Link href/onSelect issues

I am using React Bootstrap NavLink to main navigation. What I'm trying to do is:
Allow opening in new tab with correct route (no checking if it should be left needed)
If user wants to change current tab page - check if it should be left (eg some changes on current page may not be saved)
My current version looks like this:
{paths && paths.map(path =>
<NavLink href={path} onSelect={this.handleOnSelect}>
{path}
</NavLink>
}
And works quite alright, but the problem starts when my url looks like domain.com/a/b. Let's assume I click on c, navLink creates route domain.com/a/c instead of domain.com/c. I tried using href={`/${path}`} and routing worked fine but it was ignoring onSelect.
Does anyone have an idea how this could be solved? Is that even possible to accomplish?

How to target specific components through Links in react-router?

I want to insert links throughout the content of a React app, where links trigger 'navigation' only within specific Components in the tree. Ideally I would use a react library to do this, rather than invent my own framework from scratch.
Existing approaches that I can find, such as react-router, seem to assume that every routed component should only be visible when a route path matches it, rather than routes being able to selectively send 'control' signals to matching components, while unmatching components should not be affected at all.
My intended application needs independent navigation within different panes, similar to the behaviour of a HTML Frameset ( see e.g. this JavaDoc single-page navigation - https://docs.oracle.com/javase/7/docs/api/ ) where specific links have a href (a route in React) but also a 'target' which indicates which pane needs to be affected by a given navigation.
I am aware I could write a bespoke React eventing pattern. For example I could pass hooks to make changes through tree state, with my own bespoke hash or history eventing in place to monitor clicks. Before I consider writing my own framework for this, I want to understand what other approaches there are and I think I must be overlooking something obvious.
I have put together a repository which simplifies the problem from a react-router point of view.
https://github.com/cefn/graphql-gist/tree/fde58e9cf5d321d1edf3b916da4bdd95b79751a1/react-router-frames
This app has 'Frames' with embedded links. However, every Frame's component in the React tree has to be switched out for another matching component (or none) when a Link is clicked. Ideally I should be able to add a 'target' attribute or otherwise specialise a Link or target so that only a targeted part of the tree is affected by a matching link.
It should be possible for example, to cause the color of the name='left' or name='right' frames to change independently in https://github.com/cefn/graphql-gist/blob/fde58e9cf5d321d1edf3b916da4bdd95b79751a1/react-router-frames/src/FrameSet.js
I am hoping for something from the mainstream react ecosystem which supports routing (e.g. well-documented components with hash listening, history support) but not where every Link affects every Route in the page.
Here is a solution which exploits react-router and isn't totally horrible.
function FrameSet(props) {
return <Router>
<FilterPath pathPrefix="/left/">
<View />
</FilterPath>
<FilterPath pathPrefix="/right/">
<View />
</FilterPath>
</Router>
}
A FilterPath always passes on its pathPrefix value to its childrens' props, and optionally, (when the react-router location matches the pathPrefix) passes on a pathSuffix too.
In this way, each View above only receives a pathSuffix update when a route path begins with their pathPrefix and hence 'targets' them.
A draft working implementation of FilterPath is...
function FilterPath(props) {
return <Route render={({ location: { pathname } }) => {
const { pathPrefix } = props
let pathSuffix
if (pathname.startsWith(pathPrefix)) {
pathSuffix = pathname.slice(pathPrefix.length)
}
return React.Children.map(props.children, child => React.cloneElement(child, { pathPrefix, pathSuffix }))
}} />
}
A working example using FilterPath can be seen at https://github.com/cefn/graphql-gist/blob/2de588be6c2d30b92d452f71749377c9dc6c19c7/react-router-frames/src/FrameSet.js#L22

How to override Back button when a drawer is showing in React 16 / Router V4

I have a structure like this, there's a route like:
<Route path="/sample-route" component={ComponentA}/>
Then ComponentA has:
<ComponentA>
<ComponentB/>
<ComponentC>
<MaterialUIDrawer/>
</ComponentC>
</ComponentA>
ComponentC is used in 5 different routes as a child. The MaterialUIDrawer is showing based on a flag in a redux reducer. The problem I'm trying to solve is when the drawer is open, clicking back hides it but also navigates back. I tried solving it like this:
window.onpopstate = (e) => {
if (this.props.isOpen) {
this.props.toggleDrawer(false);
this.props.history.replace(this.props.match.url);
}
};
This has 2 issues:
If this is the first page you land on, it doesn't actually do anything, the function doesn't trigger
If you are on a different site and navigate to the url that contains the drawer, hitting Back moves you to a different URL (different domain)
I also tried setting a <Route/> in ComponentC and then the drawer lives there, but I didn't manage to get it to work, maybe the path is wrong. It felt like this might be the right way though, if path is /path1, then drawer lives in /path1/drawer, or /path2/drawer, etc.
I'm trying to find a way for the Back button in a browser to close the drawer (so execute a function I define) and override the default functionality.
I think you should be using props here instead of paths, so something like /path1?drawer=1, but you will definitely need to use history/location so that the back button can actually go back, so you are on the right trail.
I'm not sure what you are using for a browser history manager, but I would recommend tying off of that instead of leaning on the window pop-state. Your history module should be the source of truth and feed redux, not the other-way around IMO.

Semantic-UI-React Responsive component renders element in spite of minWidth/maxWidth prop

I have a component where I want to render different components based on screen size. If I reload the page while on mobile view, everything is ok, NavBarMobile is rendered and NavbarDesktop is not.
If I reload the page while on desktop view, then my NavbarMobile is rendered again instead of NavBarDesktop.
If I start resizing the screen to mobile and back to desktop view, NavBarDesktop is rendered correctly.
So, the problem is first page load while in Desktop view, how to fix that?
const { mainAppComponents, } = this.props
const { visible, } = this.state
return (
<Fragment>
<Responsive maxWidth={767}>
<NavBarMobile
onPusherClick={this.handlePusher}
onToggle={this.handleToggle}
rightItems={rightItems}
visible={visible}
>
{mainAppComponents.header}
{mainAppComponents.routes}
</NavBarMobile>
</Responsive>
<Responsive minWidth={768}>
<NavBarDesktop rightItems={rightItems}>{mainAppComponents.header}</NavBarDesktop>
{mainAppComponents.routes}
</Responsive>
</Fragment>
)
Igor-Vuk, I put together a quick codesandbox example just to make sure there was not a problem with how you are trying to implement the min/max width props. As you can see from this example, they do in fact work as expected. https://codesandbox.io/s/98pk46l7vr
Without seeing the rest of your component, or application, the issue may be due to something in your router. I'd recommend trying to remove some of the other components you are returning as children of the Responsive component to see if it starts working as expected (like in my codesandbox example). If it works, then you know the problem is somewhere in the children. If it does not work then there is a greater problem above in your app.
If you are using SSR, on initial load the content was rendered and served with the Responsive component having no knowledge of the viewport. So you may need to also add a CSS media query.

Resources