React: change url without rerender; using window.history? - javascript

I have a "settings" page in my react app. The page has several tabs rendering different parts of settings.
It would be better UX if a user can share urls with other users.
What I want is (inside "settings" page):
user A clicks a tab
url changes with a #tabname appended
user A send that url to user B, and user B open that url
user B sees the same tab as user A
But with react router, the whole page re-renders if the url changed:
import { withRouter } from "react-router-dom"
const MyComp = (props) => {
...
const onTabChange = () => {
// append #tabname here
props.history.replace(...); // or `push`
...
}
...
export default withRouter(MyComp)
}
After a lot of searches, I found a solution to use window.history:
const onTabChange = () => {
window.history.pushState(null, null, "#tabname");
...
}
This does the trick, but little information and explanation, and I'd love to know the consequences of using this trick.
Is this a valid solution (for a react app)? Will this cause any problem?
(PS. I know how to parse a url)
More details:
To be more specific, there is a AuthChecker wrapper for all pages. When react router's location changes, it checks for the route's allowed auths and current user's auth.
I've tried /path/:id and everything but all change location, so auth checked and page rerendered.
And I've given up a solution in react router and just want to know: is it safe to change url with window.history in a react app using react router to manage routes?

this question is already answerd at this post.
so it says window has a property called history and there is a method on history which helps you update the history state without react-router-dom understanding it.
like this:
window.history.replaceState(null, 'New Page Title', '/new_url');

Related

Get Target Path in Gatsby [duplicate]

This question already has answers here:
How to get previous url in react gatsby
(4 answers)
Closed 2 years ago.
Is there any way to find the path of the page that a user is on on a Gatsby site and store it in a const so that it can be checked next to the current path the user is on? In other words if they're on the /about page, and are clicking a link to navigate to the /contact page, I need to get the paths of both so that I can check them against each other. I know I can get location.pathname for the current url, but how do I find the path that they are navigating to when the click a link?
Since your code structure isn't clear. Assuming a simple anchor tag, you can do something like this:-
<a href="/new/link" onClick={getHref}>New Link</a>
And in your getHref method
function getHref(event){
console.log(event.target.href); // should log '/new/link'
}
Check if this works in your case.
Please forgive for any typo, I havent validated it.
Gatsby exposes props (because it extends from #reach/router from React) by default on the top-level components (this means pages). You can pass it to the child components as you wish or store it in a const or in a React's state.
Without knowing any page structure, I'll provide something dummy as an example:
import React from "react"
import { graphql } from "gatsby"
const YourPage = ({ location, data }) => {
console.log('your page is ', location.pathname)
return <div>Dummy content</div>
}
export default Page
export const query = graphql`
query PageQuery {
site {
siteMetadata {
siteURL
}
}
}
`
Your information is stored under props.location, that's why you can destructure it in the component's declaration.
In the case above, I've used pathname property but you have a bunch exposed. Check it out to find out which one fits your requirements.

history.push using react-router-dom

I am navigating from one page to another using history.push which is available from below
import { withRouter } from 'react-router-dom
I am able to navigate properly but i have a requirement that if i move from Page A to Page B, i should not be allowed to go back to previous page using Browser back button.
I know this can be achieved by window.redirect but i dont want to use that. The problem with that is the entire state and redux store information is lost. Does anyone know if i can use withRouter and still be able to achieve the requirement above.
You could use the history.replace('/Whatever_screen') to replace the current page in the stack.
replace(path, [state]) - (function) Replaces the current entry on the history stack.
Second option:
You could use the below code to block the user to going back in the history.
componentDidMount() {
const { history } = this.props;
window.onpopstate = function (event) {
history.go(1);
};
}
Working Example:
MDN reference here:
There is no way to clear the session history or to disable the back/forward navigation from unprivileged code. The closest available solution is the location.replace() method, which replaces the current item of the session history with the provided URL.

how to emulate messages/events with react useState and useContext?

I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs

prevent react-router from detecting location change

In my React/Redux based application, I have implemented logout like following
In reducers/index.js where I do combineReducers, I have created an app level reducer called appReducer. There I check for LOGOUT action and then return undefined.
All this works fine. What I want to do is, that for LOGOUT action, I also want to clear localStorage and redirect to login page. Please note that I want to redirect native browser way, not using react-router. If I do window.location = '/'. First its detected by react-router and I see login page for a bit and then it refreshes which is a bit odd.
Is there a way to prevent react-router from being notified on location change!?
You cannot prevent it completely but you can control it with the access to
history.listen function.
With React-Router 4 you can wrap top level components using the HOC withRouter.
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
This allows accessing this.props.history and controlling it
class App extends Component {
constructor(props) {
super(props);
this.props.history.listen((location, action) => {
//here you can control the location change
});
}
render() {
return (
</div>
);
}
}
And you also have the listenBefore event on the history object and you can use this event to control the navigation and add your own custom navigation logic:
history.listenBefore( (location, done) => doSomething(location).then(done) )

How to find Popup state and refresh state in React-router?

I know this question kind a stupid But I am pretty confused with this site producthunt how they are doing this.When clicking the product list popup with react router is done like this..
But When I refresh that page it render like this..How this is done using React-router
My bet would be that they use the state property when pushing a page to give an indication to the component about how to render the page. More specifically, to indicate the component where it comes from. For example:
router.push({
pathname: '/posts/origami-studio-by-facebook',
state: { fromPosts: true }
})
And then you can read the router's state in the route's component to check what page to show.
const Post = (productName) => {
if(this.context.router.location.state.fromPosts) {
return <Posts productPopup{productName} />
// open the posts page with a popup for the product
} else {
return <PostPage productName={productName} />
}
}
So when you open the page in your browser, the state.fromPosts is not set and you get redirected to the PostPage. In the end, even if the route is the same, what you end up seing is completely different.

Categories