Best way to generate role based sidebar navigation with React Router - reactjs

What is the best way to handle role a based navigation sidebar with React Router? Right now I am repeating myself by having two sidebars. I was thinking about mapping over to generate but didn't know if React Router had some functionality already to generate Links based on a Role condition? If not I am checking if there is a better approach than something like this this below?
Currently I am just hardcoding the Sidebar links instead of dynamically generating.
```
const routes = _.map(props.links, (link) => {
if(someRoleOnUser in link.roles) {
return (<div><Link .... /></div>);
}
})
```

Related

Algolia: Export React Instant Search results

Is there any way to export Algolia's React Instant Search results to a CSV? I've tried using the react-csv package, but it doesn't work with Algolia's Hit Component. The package requires data as props, but the data is constantly changing since it's React Instant Search.
What I mean by constantly changing is that on page load, you're given the entire index of records found, then you can narrow down the results with the search bar or other filtering components.
I've gone down the Google rabbit hole looking for information about exporting Algolia's search results as a CSV, but I haven't found anything regarding React Instant Search—unless I completely missed it.
Has anyone tried this before? If so, could you point me in the right direction regarding documentation or examples?
Not sure if this solves your problem but one possible way is to use the StateResults widget. The StateResults widget provides a way to access the searchState and the searchResults of InstantSearch.
Here I will create a custom StateResults component in the form of a download button and then connect it using the connectStateResults connector.
I have attached a demo below as well.
For simplicity I didn't format the data to be fed into the CSV builder.
// 1. Create a React component
const StateResults = () => {
// return the DOM output
};
// 2. Connect the component using the connector
const CustomStateResults = connectStateResults(StateResults);
// 3. Use your connected widget
<CustomStateResults />
In your case something like
const StateResults = ({ searchResults }) => {
const hits = searchResults?.hits;
return (
<div>
<button>{hits && <CSVLink data={hits}>Download CSV</CSVLink>}</button>
</div>
);
};
const DownloadButton = connectStateResults(StateResults);
//in your JSX then <DownloadButton />

Docusaurus 2 How to add custom react component in navbar

From the docusaurus docs, the navbar items can only have certain types like link, dropdown and search.
How do I add custom buttons like if I want to add login button ?
This would really depend on the type of functionality you're wanting to see out of the item you add to the navbar, but the development patterns should be similar across various implementations.
If you're trying to trigger something like a login modal when the user clicks your custom button, you could specify the following in docusaurus.config.js:
module.exports = {
themeConfig: {
navbar: {
items: [
{
href: '#login',
label: 'Login'
}
]
}
},
scripts: [
'https://yourdomain.com/customscript.js'
]
};
Then in a script, customscript.js, you could include the following:
document.querySelector('[href="#login"]')
.addEventListener('click', () => {
console.log('Login button clicked.');
});
Docusaurus requires that either href or to is given on each navbar item, so that's why I chose the weird selector, but if you wished, you could also specify className on the item, and then use that as a selector too. If you want the item to be something other than a link, you could set the outerHTML in your custom script or use replaceWith().
Keep in-mind that depending on the way your site's routing is configured, you may need to re-apply the logic in your custom script if the link node is re-written to the DOM by React.
As far as I know, there isn't really a perfect way to accomplish this at the moment, but v2 is also still in development, so the plugin exposures are getting better with each release.
The temporary workaround works well
https://github.com/facebook/docusaurus/issues/7227

Handle back button with react router

If a user navigates to www.example.com/one and clicks the back button, I want to redirect them to www.example.com.
I think it's a common problem, but I haven't found a solution yet.
Hooks version (React 16.8+):
Minimal version.
import { useHistory } from "react-router-dom";
export const Item = () => {
let history = useHistory();
return (
<>
<button onClick={() => history.goBack()}>Back</button>
</>
);
};
In react-router-dom v6 useHistory() is replaced by useNavigate(). so use useNavigate() inplace of useHistory() this way.
import { useNavigate} from "react-router-dom";
export const Item = () => {
let navigate = useNavigate();
return (
<>
<button onClick={() => navigate(-1)}>Back</button>
</>
);
};
for more on useNavigate visit this: https://reactrouter.com/docs/en/v6/hooks/use-navigate
You can try with two options, either you can use push method or goBack method from history of the router. Normally history props will available if you directly route the component via Route method or pass the history props to child component and use it.
Sample Code given below
this.props.history.push('/') //this will go to home page
or
this.props.history.goBack() //this will go to previous page
For your problem you try with push method and give the exact url you to move on.
For more reference visit https://reacttraining.com/react-router/web/api/history
What you want is this:
Let's say a person goes to a single page in your website such as: www.yoursite.com/category/books/romeo-and-juliet
In this page, you want to show a "Back" button that links you to one upper directory which is: www.yoursite.com/category/books/
This is breadcrumb system that we famously had in vBulletin forums and such.
Here is a basic solution to this:
let url = window.location.href;
let backButtonUrl = "";
if (url.charAt(url.length - 1) === "/") {
backButtonUrl = url.slice(0, url.lastIndexOf("/"));
backButtonUrl = backButtonUrl.slice(0, backButtonUrl.lastIndexOf("/"));
} else {
backButtonUrl = url.slice(0, url.lastIndexOf("/"));
}
What it basically does is:
1. Get the current URL from browser
2. Check if there is a "/" (slash) at the end of the link.
a. If there is: remove the slash, and remove everything the last slash
b. If there is not: remove everything last slash.
You can use {backButtonUrl} as your Go Back button link.
Note: it does not have anything to do with React Router, history, etc.
Note 2: Assuming you are using a link architecture that goes like www.site.com/word/letter/character
If you want to use it with react-router-dom library, then you need to set your url variable like this:
let url = this.props.match.url;
I found a solution. It's not beautiful but it works.
class Restaurant extends Component {
constructor(props) {
super(props);
this.props.history.push('/');
this.props.history.push(this.props.match.url);
}
...
I've had the same problem today. I have the following flow in one of the applications I'm working on:
User fills out a registration form
User enters credit card "payment page"
When payment is successful, the user sees a "payment confirmation" page.
I want to prevent the users from navigating from the "payment confirmation" (3) page back to any previous steps in the payment flow (1 and 2).
The best thing to do would be not to use routes to control which content is displayed, and use state instead. If you cannot afford to do that,
I found two practical ways to solve the problem:
Using React-Router:
When you hit the back button, React Router's history object will look like this:
When you go to any page using history.push record the page you are visiting in the state
Create a decorator, HOC, or whatever type of wrapper you prefer around the React-Router's Route component. In this component: If history.action === "POP" and "history.state.lastVisited === <some page with back navigation disabled>", then you should redirect your user to the /home page using history.replace
Another way to do is is by going to the /home page directly:
Use history.action to detect the back button was used, then:
Change location.href to equal the page you want to visit
Call location.reload(). This will reload the application, and the history will be reset
Browser back button works on your routes history. It will not invoke your programmatically handled routing. That's the point where we should keep maintain history stack with react router. If you are at route '/' and push '/home'. On browser back button it will pop '/home and will go back to '/'.
Even If you implementButton component for go back functionality and use react router history props. Believe me you have to carefully manage your navigation to maintain browser history stack appropriately. So it behaves same like whether you press browser back button or your app Button to go back or go forward.
I hope this would be helpful.
We wanted something similar for our React app and unfortunately this was the best solution we came up with. This is particularly helpful when our users are on mobile devices and they land on a specific page on our site from an ad or a referrer.
This is in our main routes.tsx file.
useEffect(() => {
// The path that the user is supposed to go to
const destinationPath = location.pathname;
// If our site was opened in a new window or tab and accessed directly
// OR the page before this one was NOT a page on our site, then...
if (
document.referrer === window.location.href ||
document.referrer.indexOf(window.location.host) === -1
) {
// Replaces the current pathname with the homepage
history.replace("/");
// Then pushes to the path the user was supposed to go to
history.push(destinationPath);
}
}, []);
Now when a user presses the back button, it takes the user to our homepage instead of being stuck within the "nested" route they were in.
NOTE: There are some small quirks with this implementation. Our app is also a Cordova app so we NEED to have our own back button. This implementation works well with our own back button but does not seem to work with the native browser's back button; hence, it worked well for our needs.

React Router: Query Param Match?

According to the accepted answer to this question, React Router 4 doesn't match query parameters anymore. If I go from a URL matched by one of my <Route>s to the same URL with a different query string, the content doesn't seem to change. I believe this is because navigating between URLs that match the same <Route> doesn't change the content, but please correct me if I'm wrong. Given this, how do I use React Router for a set of URL's that need to differ only by query parameter?
For example, many search engines and other sites that use search bars, including the site I am working on, use a query parameter, commonly q or query. The user may search for one thing, then decide that is not what he/she wants and search for another thing. The user may type in the second URL or search with the search bar again. There isn't really a place for the search term in the URL path, so it kind of needs to go in the query string. How do we handle this situation?
Is there a way, with React Router, to link to a URL that only differs in the query string and change the content, without refreshing the entire page? Preferably, this wouldn't require any external library besides React and React Router.
Try the render function prop instead of component prop of Route. Something like this:
<Route render={props => {
// look for some param in the query string...
const useComponentA = queryStringContains('A');
if(useComponentA) {
return <ComponentA {...props}/>;
} else {
return <ComponentB {...props}/>;
}
}}/>
There are 2 ways to do that:
1) Use location.search in react component to get the query string, then pass it to child component to prevent re-rendering the whole component. React-router has the official example about this.
2) Define a regex path of router to catch the query string, then pass it to react component. Take pagination as an example:
routes.js, for router config you can refer this
const routerConfig = [
{
path: '/foo',
component: 'Foo',
},
{
path: '/student/listing:pageNumber(\\?page=.*)?',
component: 'Student'
},
Student.js
render() {
// get the page number from react router's match params
let currentPageNumber = 1;
// Defensive checking, if the query param is missing, use default number.
if (this.props.match.params.pageNumber) {
// the match param will return the whole query string,
// so we can get the number from the string before using it.
currentPageNumber = this.props.match.params.pageNumber.split('?page=').pop();
}
return <div>
student listing content ...
<Pagination pageNumber = {currentPageNumber}>
</div>
}
Pagination.js
render() {
return <div> current page number is {this.props.pageNumber} </div>
}
The 2nd solution is longer but more flexible. One of the use cases is server sider rendering:
Apart from the react components, the rest of the application (e.g. preloaded saga) need to know the url including query string to make API call.

Best practice for dynamic routing (react router v4) needed?

With React Router V4 being out only for a little while and there being no clear documentation on dynamic routing [akin to transtionsTo(...) in V3] I feel like a simple answer to this question could benefit many. So here we go.
Lets assume the following theoretical scenario: one has a component Container, which includes two other components (Selection and Display). Now in terms of functionality:
Container holds a state, which can be changed by Selection, Display shows data based on said state.
Now how would one go about changing the URL as well as the state triggered by a change in state via react router?
For a more concrete example please see (React Router V4 - Page does not rerender on changed Route). However, I felt the need to generalize and shorten the question to get anywhere.
Courtesy to [Tharaka Wijebandara] the solution to this problem is:
Have the Container component provide the Selection component with a callback function that has to do at least the following on Container:
props.history.push(Selection coming from Selection);
Please find below an example of the Container (called Geoselector) component, passing the setLocation callback down to the Selection (called Geosuggest) component.
class Geoselector extends Component {
constructor (props) {
super(props);
this.setLocation = this.setLocation.bind(this);
//Sets location in case of a reload instead of entering via landing
if (!Session.get('selectedLocation')) {
let myRe = new RegExp('/location/(.*)');
let locationFromPath = myRe.exec(this.props.location.pathname)[1];
Session.set('selectedLocation',locationFromPath);
}
}
setLocation(value) {
const newLocation = value.label;
if (Session.get('selectedLocation') != newLocation) {
Session.set('selectedLocation',newLocation);
Session.set('locationLngLat',value.location);
this.props.history.push(`/location/${newLocation}`)
};
}
render () {
return (
<Geosuggest
onSuggestSelect={this.setLocation}
types={['(cities)']}
placeholder="Please select a location ..."
/>
)
}
}

Resources