React redirection page with history.push give a bad url - reactjs

I have a button and when press this button execute the function createEvent and this put in the page with url
mypage.com/event/create
If I press this again because is a component that I used in all page even in th create my url is:
mypage.com/event/event/create
and get error page not found
my function:
const history = useHistory();
const createEvent = () => {
history.push('event/create');
};

Related

Navigate the user to another URL using useNavigate() of react-router-dom-v6 [duplicate]

This question already has answers here:
React Router Dom, useNavigate not redirecting to the right url path
(2 answers)
Closed yesterday.
I am working on a React 18 application and I am trying to navigate the guest user to the shipping page (URL: localhost:3000/shipping) from the cart page by clicking the Proceed to Checkout button. On clicking the button, the user should be first re-directed to the sign in page where he should sign in (on the SigninScreen page) and then proceed to the shipping page.
The current url when clicking the Proceed to Checkout button is (URL: localhost:3000/signin?redirect=shipping)
On clicking the SignIn button on the SignInScreen page I do a redirect in the useEffect() hook:
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
const userSignin = useSelector((state) => state.userSignin);
const { userInfo, loading, error } = userSignin;
const [searchParams] = useSearchParams();
const redirectUrl = searchParams.get('redirect') || '/';
const navigate = useNavigate();
useEffect(() => {
// If the user is logged in, the user should redirected to the shipping page.
if (userInfo) {
navigate(redirectUrl, {replace: true});
}
}, [userInfo, redirectUrl, navigate]);
Even after using {replace: true} the user does not get navigated to localhost:3000/shipping, instead it gets redirected to localhost:3000/signin/shipping
Could someone please guide me on what am I doing wrong?
Tried: Even after using {replace: true} the user does not get navigated to localhost:3000/shipping, instead it gets redirected to localhost:3000/signin/shipping
Expectation: The user should navigate to localhost:3000/shipping from localhost:3000/signin?redirect=shipping on signing the user in on the sign in screen by clicking the Sign In button
URLs which do not start with a / are treated as relative links to the current route. You are navigating the user to shipping when it should be /shipping.
Here’s one way to add the starting slash:
const redirectUrl = '/' + (searchParams.get('redirect') || '');

Browser back button breaks React app history

I have ran into a very weird behaviour with the browser back button. I’ve tested on Chrome and Safari, the behaviour is the same.
So, I have a React app that has 3 pages - home (/), signup (/email) and success (/success). The routes are setup via react-router. Every page is in it's own functional component.
The user moves from home to signup via a button and a history.push(). From signup to success via a submit button and a history.push(). Something like this:
const onClick = () => {
history.push({ pathname: '/email', search: window.location.search });
};
Once on the success page, if the user waits 5 seconds, they will be redirected to an eCom website, via window.location.assign():
const useRedirectTimer = (
url: RedirectUrl,
): {
redirectTimerRef: React.MutableRefObject<NodeJS.Timeout | undefined>;
clearRedirectTimer: () => void;
} => {
const history = useHistory();
const { search } = useLocation();
const redirectTimerRef = useRef<NodeJS.Timeout | undefined>();
const clearRedirectTimer = () =>
redirectTimerRef.current && clearTimeout(redirectTimerRef.current);
useEffect(() => {
redirectTimerRef.current = setTimeout(() => {
window.location.assign(url.url);
}, delayInMS);
return () => {
clearRedirectTimer();
};
}, []);
return { redirectTimerRef, clearRedirectTimer };
};
Now, this is where I start having issues. There are 2 main situations:
Not waiting for the eCom website to load fully after the redirect and clicking the browser back button: I am returned to the browser's start page and my app basically disappears from history.
Waiting to be redirected to the eCom website, waiting for the full load, going back to /success page, waiting to be redirected and eCom website be fully loaded again and then pressing the back button - (so basically doing the same thing twice in a row) I am either returned to my app's home page (rarely) or to the browser's start page (more commonly).
Adding a button to the success page and making it do the redirect on click, or even just clicking anywhere on the page and then waiting for the redirect solves the second issue. It seems that no matter how many times I go back and forth, I always end up on the success page.
So my hunch is that at least one element on the page has to be focused once so the page can be returned to. But I could of course be wrong.
Is there any way I can make history preserve my app no matter how many times I go back and forth, and to always return to the success page from the eCom website?

Why am I redirect to signin page while testing App component w/ Jest?

I am testing my App component by doing the authentication process (i start at landing page then click on button sign in to be redirect to the signin page , fill the email and password field and click to the signin button to access to the first page which is installations page in my case) but after a login successful response from the server, I am redirected to the sign in page and i can't figure out why. Here is the test in question:
it( 'Login', async () => {
render(<App />)
// Check we are on the good location at the begining of the test
await waitFor(() => expect(window.location.pathname).toBe('/'))
// env variable needed to connect to the account
const email = process.env.TEST_EMAIL
const password = process.env.TEST_PASSWORD
// // signIn button from the landing page
const signInButton = screen.getByTestId('landing-signin')
expect(signInButton).toBeInTheDocument()
// click action of this button
fireEvent.click(signInButton)
// check the path
await waitFor( async () => expect(window.location.pathname).toBe('/signin'))
// check the presence of elements in signin page
const emailField = screen.getByPlaceholderText('Email address')
const passwordField = screen.getByPlaceholderText('Your password')
expect(passwordField).toBeInTheDocument()
expect(emailField).toBeInTheDocument()
// fill the credentials
userEvent.type(emailField, email)
userEvent.type(passwordField, password)
const button = screen.getByTestId('submit-button')
expect(button).toBeInTheDocument()
// fire event login by clicking the button
act(() => {
userEvent.click(button)
})
// // check the path
await waitFor( async () => expect(window.location.pathname).toBe('/installations'))
// check the title of installation page to be in the document
const title = screen.getByText('Installation selection')
expect(title).toBeInTheDocument()
// check info to be in the document
const infoText = screen.getByText('Please, wait a little bit while we are fetching {{data}}')
expect(infoText).toBeInTheDocument()
// back to /signin
console.log('window.location.pathname', window.location.pathname)
})
I have jsdom enable, I am using Jest and React-testing-library. I find some elements of installation page but I get redirect to the sign in page.

How to redirect user back to initially-requested page after authentication with React-Router v6?

I have a situation where, if a user isn't authenticated, they get redirected to the login page.
Once the user logs in, they get sent back to the main page.
This is a pretty typical situation with React, but I'm wondering how I can redirect the user back to a certain page after they authenticate.
Suppose the user tries to access /app/topics, which would be a private route. They would get redirected to /, where they have to authenticate. Afterwards, they get redirected to /app/about once they authenticated.
How can I ensure that the user gets redirected back to /app/topics instead?
The About component would look something like,
const About = ({ user }) => {
const navigate = useNavigate();
React.useEffect(() => {
if (!user) navigate("/");
}, [user]);
return (
<div>
<h2>About</h2>
</div>
);
};
export default About;
And Home (or the 'login page') would look like this,
const Home = ({ user, setUser }) => {
const navigate = useNavigate();
React.useEffect(() => {
if (user) navigate("/app/about");
}, [user]);
return (
<div>
<h2>Login</h2>
<input />
<button onClick={() => setUser(1)}>login</button>
</div>
);
};
export default Home;
I'm aware the this line,
if (user) navigate("/app/about");
Is why the user gets redirected to About upon authenticating, but I'm wondering how I can change this up, so that the user is redirected back to Topics.
I've tried a combination of different approached. The most promising thing that I've tried was saving the requested uri into my Redux state. This cause issue with my existing code, however.
I've also tried saving state with Navigate or useNavigate.
If I recall correctly, React-Router v5 had a redirect prop or something of the sort, that redirected the user back to a certain page.
I would just fallback to good old query parametr in url, just upon redirecting to login page, put query param, from or something in url, and upon successfull login do the redirect, this has the huge advatage that if you take him to different page or for some reason he has to reload page, he keeps this info.
React router v6 documentation provides an exemple that answers you requirements, here is their sandbox.
They use the from property of location's state:
function LoginPage() {
let navigate = useNavigate();
let location = useLocation();
let auth = useAuth();
let from = location.state?.from?.pathname || "/";
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
let formData = new FormData(event.currentTarget);
let username = formData.get("username") as string;
auth.signin(username, () => {
// Send them back to the page they tried to visit when they were
// redirected to the login page. Use { replace: true } so we don't create
// another entry in the history stack for the login page. This means that
// when they get to the protected page and click the back button, they
// won't end up back on the login page, which is also really nice for the
// user experience.
navigate(from, { replace: true });
});
}

Reactjs SPA - Pressing IOS Safari back button updates the url but not the component view

We have a reactjs SPA application where we want to navigate back and forth using browser back button/swipe the screen, if on Mac.
when a user clicks on a link from home page, they will be navigated to a detail page and the url looks like https://example.com/pdetail?prdt=Computer
On detail page, User has an ability to search, and when the user searches(Say Stationery), we update the url and push the url to history and the the detail page component is updated with the searched data.Like
https://example.com/pdetail?prdt=Stationery
filterSearch(searchval) {
//search related code to set variables
let newUrl = setParams(searchval, 'prdt')
this.props.history.push(`?${newUrl}`);
// dispatch an api call to get data
}
export function setParams(query = "", param) {
const searchParams = new URLSearchParams(window.location.search);
searchParams.set(param, query.trim());
return searchParams.toString();
}
Now when the user browse back using the browser back button, we expect it to go the previous data(https://example.com/pdetail?prdt=Computer) from (https://example.com/pdetail?prdt=Stationery) and Vice Versa as we move back and forth. It is working fine in chrome and IE.
But in Safari, as the user presses back button, url is changing in the browser but the component view is not.
as we navigate back, I noticed that it did not go to ComponentDidMount as we go to the same page. But it goes ComponentDidUpdate so I added my code in there.
componentDidUpdate() {
window.onpopstate = (e) => {
if(this.props.history.action == 'POP')
{
e.preventDefault();
const search = window.location.search;
const params = new URLSearchParams(search);
if(params.has('prdt') === true)
{
const selectedprdt = params.get('prdt');
this.props.fetchDetails('FETCH_PRDT_DETAILS' , selectedprdt);
}
}
}
}
Let me know how can get this back forth page navigation with consistent url and data in Safari.
It is working fine in IE and chrome.
EDIT:
I have edited my code to add an eventListener in componentDidMount and UI is working in Safari. But when I see in the logs, I noticed the event runs multiple times not just once, which is very inefficient as we are making server calls.
componentDidMount()
{
const selectedprdt = getParams(window.location, 'prdt');
if(selectedprdt !== '')
{
this.props.fetchDetails('FETCH_PRDT_DETAILS' , selectedprdt);
}
window.addEventListener('popstate', this.handleBackButton)
}
handleBackButton = (e) =>
{
if(this.props.history.action == 'POP')
{
const search = window.location.search;
const params = new URLSearchParams(search);
if(params.has('prdt') === true)
{
const selectedMarket = params.get('prdt');
this.props.fetchDetails('FETCH_PRDT_DETAILS' , selectedprdt);
}
e.preventDefault();
}
}
Please let me know how to stop eventListener to executing the calls multiple times.
Thanks,
I had to remove the same listener on component Unmount.
componentWillUnMount()
{
window.removeEventListener('popstate', this.handleBackButton)
}

Resources