History.push a link to a new tab with react router - reactjs

I need to open a link to a new tab after doing some logic.
I have a button like this:
<Button
onClick={handleSubmit}
>
Preview
</Button>
with the handleSubmit() being:
const history = useHistory();
const handleSubmit = () => {
console.log("doing something");
history.push("/some-link")
}
As you can see, with my usecase it wouldn't make sense to use the Link component.
So, is there a way to push that link to a new tab, using only history.push()

React-router's history is meant to be used just for the navigation of your application and some persistance. I can't see why you need to use it to open a new tab. I think you should use the window object for this.
const handleSubmit = () => {
console.log("doing something");
const win = window.open("/some-link", "_blank");
win.focus();
}
UPDATE: Due to some comments that confirm that it is not neccesary to focus the new window we can make this solution even shorter, like:
const handleSubmit = () => {
console.log("doing something");
window.open("/some-link", "_blank");
}

If you simply want to open a new tab without any state transfer, you can just get the path using useHref hook (Docs). This is what the <Link> component internally uses. You can then open it in new tab using window.open. This would automatically add the basename etc for you.
let href = useHref(to);
window.open(href, '_blank');

Related

Prompt user when url changes using react-router-dom

Assuming I am within an edit form page that has the url route of /person/edit/123
Within my react app, I also have a website logo at the top left of my app that when clicked, returns the user to the url route /home
Using react-router-dom v6 or some other means, I need to be able to check that when a user is within an edit page and decides to click on the website logo, I need to prompt the user that changes have been made and provide some message that has a "Leave page yes/no dialog"
Unsure what approach to take inorder to accomplish the above.
I have seen other threads within SO but they are using older versions of react-router-dom.
Any guidance would be great.
UPDATE: Code used but didn't seem to work:
useEffect(() => {
window.addEventListener('beforeunload', function (e) {
var confirmationMessage =
'It looks like you have been editing something. ' +
'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
}, []);
This demostrate a simplified version of a complex example to manually check for dirty forms, instead of relying on unload.
Considering you have a mechanism to check for dirty forms.
e.g.
const Component = ({ text }) => {
const [ edited, setEdited ] = useState(text)
const checkDirty = () => edited !== text
return (... my form codes here...)
}
One of the solution is to create a CustomLink component. (psuedo code)
const CustomLink = React.forwardRef(({ onClick: dirty, href }, ref) => (
const beforeHref = (e) => {
e.preventDefault();
if (typeof dirty == "function") {
if (!dirty()) {
return redirect(href)
} else {
if (confirm("Should we redirect!")) {
return redirect(href);
} else {
return null
}
}
}
redirect(href);
}
// you should probably use the link component here
return <a href={href} onClick={beforeHref} {...rest} />
));
Then in the page you can create a link like
<CustomLink href="/somepage" onClick={checkDirty} />
PS: Of course in the overall pages, you must pass those props to your menu, and your logo.

Fresh Call To API EndPoint on Button Click in React JS

I haven API endpoint, that gives me a random text, on each call. As of now, when the React Component loads for the first time, a call goes through to the API and I get the random text.
The code looks like this. I am using redux for state management.
const RandomQuoteList = ({ todo, isLoading, startLoadingTodos }) => {
useEffect(() => {
startLoadingTodos();
}, []);
const [inputValue, setInputValue] = useState(`HelloThere`);
function changeRandomText(e) {
// const item = e.target.value;
var something = Math.random();
console.log(`changeRandomText clicked + ${something}`);
setInputValue(`changeRandomText clicked + ${something}`);
console.log(inputValue);
}
const loadingMessage = <div>Loading todos...</div>;
const content = (
<GeneralWrapper>
<RandomQuoteItem todo = {todo} inputValue = {inputValue}/>
<Button onClick={changeRandomText}>Get A New Quote</Button>
</GeneralWrapper>
);
return isLoading ? loadingMessage : content;
};
const mapStateToProps = state => ({
isLoading: getTodosLoading(state),
todo: getTodos(state),
});
const mapDispatchToProps = dispatch => ({
startLoadingTodos: () => dispatch(loadTodos()),
});
export default connect(mapStateToProps, mapDispatchToProps)(RandomQuoteList);
Now, I want to be able to use a simple button click to 'refresh' the API call. That way, the API endpoint will be triggered and a fresh new text will get updated.
I have looked at the following stack over flow questions.
React: re render componet after button click
How to refresh React page/component on Button click after POST
ReactJs : How to reload a component onClick
But, I am not getting far. I am able to randomly change the state of a text component, and that is changing the text component. So, I have the random value change part taken care of.
The target component looks something like this. When I click the button on the above component, the below component updates the random text no problem.
const RandomQuoteItem = ({ todo,inputValue }) => {
//set the style for the display.
// const Container = todo.isCompleted ? TodoItemContainer : TodoItemContainerWithWarning;
const Container = TodoItemContainer;
return (
<Container>
{/* this is where you show your API response single items. */}
<h3>{todo.quoteContent}</h3>
<h4>{todo.quoteAuthor}</h4>
<h4>{todo.quoteIdentifierString}</h4>
<p>-------------------------------</p>
<h4>{todo.quoteIdentifierCompadre}</h4>
<h4>{todo.dateTimeOfResponse}</h4>
<h4>{todo.detailsAboutOperation}</h4>
<p>{inputValue}</p>
</Container>
);
}
Now, how do I link this random state change to my RandomQuoteItem state, so, it makes fresh data call?
Based on the comment from rahuuz above, I ended up with this. and it worked.
function changeRandomText(e) {
// const item = e.target.value;
var something = Math.random();
console.log(`changeRandomText clicked + ${something}`);
setInputValue(`changeRandomText clicked + ${something}`);
console.log(inputValue);
startLoadingTodos(); //this specific line solved the problem.
}
I think, it worked in my favour that I already had redux and reducers and all of that hooked up. If that was no the case, this specific solution may not have worked, I think.
Button click calls the startLoadingTodos function, which in turn calls the API and that returns data, updating the redux state, and component also updates.

How to run a function when user clicks the back button, in React.js?

I looked around and tried to find a solution with React router.
With V5 you can use <Promt />.
I tried also to find a vanilla JavaScript solution, but nothing worked for me.
I use React router v6 and histroy is replaced with const navigate = useNavigation() which doesn't have a .listen attribute.
Further v6 doesn't have a <Promt /> component.
Nevertheless, at the end I used useEffect clear function. But this works for all changes of component. Also when going forward.
According to the react.js docs, "React performs the cleanup when the component unmounts."
useEffect(() => {
// If user clicks the back button run function
return resetValues();;
})
Currently the Prompt component (and usePrompt and useBlocker) isn't supported in react-router-dom#6 but the maintainers appear to have every intention reintroducing it in the future.
If you are simply wanting to run a function when a back navigation (POP action) occurs then a possible solution is to create a custom hook for it using the exported NavigationContext.
Example:
import { UNSAFE_NavigationContext } from "react-router-dom";
const useBackListener = (callback) => {
const navigator = useContext(UNSAFE_NavigationContext).navigator;
useEffect(() => {
const listener = ({ location, action }) => {
console.log("listener", { location, action });
if (action === "POP") {
callback({ location, action });
}
};
const unlisten = navigator.listen(listener);
return unlisten;
}, [callback, navigator]);
};
Usage:
useBackListener(({ location }) =>
console.log("Navigated Back", { location })
);
If using the UNSAFE_NavigationContext context is something you'd prefer to avoid then the alternative is to create a custom route that can use a custom history object (i.e. from createBrowserHistory) and use the normal history.listen. See my answer here for details.

How peristent is router state in react?

I have an app where I set the store state as follow :
history.replace(location.pathname, {myValue: 1});
I know, one way to clear it is doing history.replace(location.pathname, {});
but I was wondering, what other way this state is replaced ?
It looks like this happen when I click on a <Link to={"/new/url"}/> but are there other situation ? how persistend is that state ?
The easiest way is to use connected-react-router if you are using Redux.
Otherwise, you have to build it by yourself.
Basically, you need to have a listener to catch the event "popstate".
A popstate event is dispatched to the window each time the active
history entry changes between two history entries for the same
document
(Mozila MDN Web docs)
Here is a very basic example:
function useLocation () {
const [location, setLocation] = useState(window.location)
useEffect(() => {
window.addEventListener('popstate', handleChange)
return () => window.removeEventListener('popstate', handleChange)
}, [])
const handleChange = ()=> setLocation(window.location);
}

Traversing to another page in reactjs inside an onclick button event

I have a simple reactjs button (Material-UI components) and I need to trigger its onclick event, inside that I call an API to the server and upon successful callback I need to transfer use to a new page, I am writing the onclick event function as follow but it doesn't work
import SignupPage from "views/SignupPage/SignupPage.js";
export default function MyPage(props) {
const [is_logged_in, set_is_logged_in] = useState(props.is_looged_in);
const [fname, set_fname] = useState(props.fname);
const [lname, set_lname] = useState(props.lname);
...
function goto(e){
axios.get('http://127.0.0.1:9000/api/is_logged_in/')
.then(res =>{
if (res.status!=200) {
throw new Error('Network response was not ok');}
return res;})
.then(res=>{
const value=res.data.res;
set_is_logged_in(value);
if (value==1){
//traverse to new page upon successful login
var uploadScreen=[];
uploadScreen.push(<SignupPage />);
}
})
}
...
<Button onClick={goto} color="primary">Evaluate</Button>
}
Can someone help to come up with correct codes?

Resources