I'm working on a reactJS web App , im stuck in this problem . I want to use or any other method to redirect to another page after testing a value from the server.
I tried this :
<Link to='/Upload'>
<Button
variant="contained"
color="primary"
onClick={async () => {
await verificationp(phone, code).then(async (result) => {
if ((await result) === true) {
//I want to redirect to another page
} else {
//I want to display an alert
}
});
}}
>
Submit
</Button>
</Link>
You don't need to use the Link component in this example. Try using the history prop (props.history.push('/')) See the below examples, the last one should apply to you:
In your App.js router:
<Switch>
<Route path="/auth" component={Auth} />
<Route path="/errorpage" component={ErrorPage} />
<Route path="/" exact component={HomeScreen} />
<Redirect to='/' />
</Switch>
On the page where you want to redirect:
import React, { useState } from 'react'
const Auth = props => {
const [form, setForm] = useState({
data: {
email: '',
password: ''
}
})
const authHandler = async (event) => {
event.preventDefault()
try {
// await dispatch(action) // dispatch to redux or send a fetch
props.history.push('/') // redirects if no errors
} catch (err) {
props.history.push('/errorpage') // redirects if an error
}
}
const inputChangedHandler = e => {
setForm({
...form,
data: {
...form.data,
[e.target.id]: e.target.value
}
})
}
return (
<div>
<form onSubmit={authHandler}>
<input id='email' type='email' value={form.data.email} onChange={inputChangedHandler} />
<input id='password' type='password' value={form.data.password} onChange={inputChangedHandler} />
<button type='submit'>Login</button>
</form>
</div>
)
}
export default Auth
In your case try the below if the page is on the route stack the history prop will be available or you will have to use withRouter or useHistory hook to get access to the history prop.
const clickHandler = () => {
try {
// await dispatch(action)
props.history.push('/') // redirects
} catch (err) {
props.history.push('/errorpage') // redirects
}
}
return (<button type='button' onClick={clickHandler}>Click Me</button)
Here is the Redirect doc
I think best thing you can do is create a separate function for onClick and change a state to redirect, so this way you can prevent some action by user while request is in progress
const [redirectTo, setRedirectTo] = useState('');
const chekcfromServer = async () => {
await verificationp(phone, code).then(async (result) => {
if ((await result) === true) {
setRedirectTo('/somewhereinyour/application')
} else {
// I want to display an alert
}
});
};
and when in render
return (
<>
redirectTo && <Redirect to={{pathname: redirectTo }} />
...rest of your code
</>
)
You can use "redirect" from "react-router-dom"
Example:
import { Redirect } from "react-router-dom";
<Link to='/Upload'>
<Button
variant="contained"
color="primary"
onClick={async () => {
await verificationp(phone, code).then(async (result) => {
if ((await result) === true) {
//I want to redirect to another page
return <Redirect to="your_url" />
} else {
//I want to display an alert
}
});
}}
>
Submit
</Button>
</Link>
I tend to use the state system (Redux) to resolve this issue.
So my problem was I wanted to fire an event.preventDefault() to stop a link being clicked, but then I wanted to do an API call after it (to check login status, as it was a complex IoT device) and click the link if the check was fine, but not click the link if it wasn't. However, the event.PreventDefault() stopped me doing this, because it cancels any events (I'm sure there are nicer ways to achieve this! but time targets and all that).
I solved this by:
Having my usual async function with the event.preventDefault() inside it.
Within the first call I set some state in Redux, just a basic boolean.
If the API check was successful then I call the same click function again (using .click()) and bypass the event.preventDefault() thanks to Redux (then reset the state afterwards) which forces the re-direct. If the check failed then I display an error message and don't display again (while obviously updating Redux).
The State Management system inside React is very powerful. You can do some hacky things! but sometimes the other "correct" solutions don't work for you; for example the history props functionality, which you can get round that other ways via State Management.
Related
I have a web page/app written in React that has two components, main page and a navigation bar at the top of the page. The navigation bar has a Google login button component that allows the user to log in with their Google account. When the user logs in, the navigation bar also has a component that shows the user name and profile picture. Here is an example screenshot:
Imgur
The problem is that when the user navigates to other pages, the avatar and text always take about a second to show up, making the page navigation janky.
When the user navigates to other page, the user avatar and name take a second to show up. This causes everything else on the page to suddenly move downwards.
Here's some parts of the code:
App.js
const App = () => {
return(
<div>
<Navigation/>
<Routes>
<Route path="/" element={<Landing/>} />
<Route path="/extrainfo" element={<ExtraInfo/>} />
<Route path="/test" element={<Test/>}/>
<Route path="/userpage" element={<Userpage/>}/>
</Routes>
</div>
);
}
Navigation.js
const Navigation = () => {
let userInfo = JSON.parse( localStorage.getItem('userInfo') );
const [isLoggedIn, setIsLoggedIn] = useState(false);
const navigate = useNavigate();
const onSuccess = async (googleRes) => {
localStorage.setItem("user", JSON.stringify(googleRes));
setIsLoggedIn(true);
try{
let userResponse = await UserService.getUser()
if(userResponse.status === 200){
setIsLoggedIn(true);
navigate("/userpage");
}
}
catch(error){
if (error.response.status === 404){
/* navigate("/extrainfo"); */
}
const resMessage = (error.response && error.response.data && error.response.data.message) || error.message || error.toString();
console.log(resMessage);
}
};
const onFailure = (err) => {
console.log('failed', err);
};
const onLogout = (res) => {
console.log("log out");
UserService.logout();
setIsLoggedIn(false);
}
useEffect(() => {
document.title = 'Navigation';
const start = () =>{
gapi.client.init({
clientId: process.env.REACT_APP_GOOGLE_CLIENT_ID,
scope: 'email',
});
}
gapi.load('client:auth2', start);
}, []);
return (
<nav className="dt w-100 border-box pa2 ph5-ns bg-#f5f5f5">
<a href="/" title="Home">
<img src="mylogo.png"
className="dib w3 h3 br-100" alt="Site Name"/>
</a>
<div className="dtc v-mid w-40 tr">
{ isLoggedIn ? (
<GoogleLogout
clientId= {process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Logout"
onLogoutSuccess={onLogout}
/>) : (
<GoogleLogin
clientId= {process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Sign in"
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
isSignedIn={true}
/>
)}
</div>
<div className="dtc v-mid w-10 tr">
<a className="link dim white f6 f5-ns dib mr3 mr4-ns bg-dark-gray pa2" href="landing" title="Landing">Enter page</a>
</div>
<MyAvatar isLoggedIn={isLoggedIn}/>
</nav>
);
}
MyAvatar.js
const MyAvatar = (isLoggedIn) => {
const usrData = JSON.parse( localStorage.getItem('user') );
let usrObj = {};
let usrName = null;
let usrImg = null;
if (usrData) {
usrObj = usrData['profileObj'];
usrName = usrObj['name'];
usrImg = usrObj['imageUrl'];
}
if(isLoggedIn.isLoggedIn === true ){
return(
<div className="dtc v-mid w-10 pa1 tc">
<img
src={usrImg}
className="br-100 h3 w3 dib" alt={usrName + ' avatar'}>
</img>
<p className="avatar_name">{usrName}</p>
</div>
)
}
}
it seems like the information tooks a bit longer then the rest to load. Usually if you would use a styling library, you could use skeletons. These elements are like placeholders which are shown until the data is loaded properly. You see them for example in many social media apps, when new content is loading but not displayed yet. But this is a intermediate thing to learn.
What could be more helpful in your case, is to move the loading part of your app from the navigation component into the main App. By this, you prevent a new loading everytime the Navigation element is rendered, which I assume causes the delay. Or, even better would be a function which is only called when someone clicks the login button.
You should have a look at stores also. I assume you're learning the basics, so stores will cross your ways sooner or later, but for your case, they are pretty good, since you can access them from everywhere and don't have to deal with propdrilling.
Just put the Userdata in a store and acess the store in the Navigation element. That should do the trick. Have a nice day and keep on learning.
I am trying to create this single-page-application in react, and I want to redirect to another page based on if the username and password inputs are correct. I have tried multiple approaches, such as the <Redirect to .. tag and history.push(), but none of them seem to be getting rendered. If I try alerts or console.log , I can see that the statement was successful, but the redirect code is never rendered.
This is how my CustomerLoginPage render method looks :
render() {
return (
<div class="App-header">
<NavLink to="/main">
<button id="backButton">Go Back</button>
</NavLink>
<h4>Login with your customer account</h4>
<br />
<input id="customerLoginUsername" placeholder=" Username"></input>
<input id="customerLoginPassword" type="password" placeholder=" Password"></input>
<br />
<button id="customerLoginButton" onClick={this.handleSubmit}>
Login
</button>
<div className="content"></div>
</div>
);
}
}
export default withRouter(CustomerLoginPage);
And this is how the handleSubmit() looks like :
handleSubmit() {
let usernameInput = document.getElementById('customerLoginUsername');
let passwordInput = document.getElementById('customerLoginPassword');
if (usernameInput.value !== 'customer' || passwordInput.value !== 'customer') {
// alert("Wrong username or password!");
} else {
this.props.history.replace('/customerhomepage');
}
}
What could I be missing here? Thanks!
EDIT
I managed to fix this issue by changing my CustomerLoginPage from a class component to a function component , and then using useHistory to change the page.
After reading the suggestions about using my Router as a HOC, I removed it from all the files except for index.js and switched to a BrowserRouter instead of HashRouter.
This is how CustomerLoginPage looks now :
function CustomerLoginPage () {
const history = useHistory();
const loginButtonClicked = () =>{
let path = '/customerhomepage';
history.push(path);
}
const backButtonClicked = () => {
let path = '/main';
history.push(path);
}
const handleSubmit = () => {
let usernameInput = document.getElementById('customerLoginUsername');
let passwordInput = document.getElementById('customerLoginPassword');
if (usernameInput.value !== 'customer' || passwordInput.value !== 'customer') {
alert("Wrong username or password!");
} else {
loginButtonClicked();
}
}
return (
<div class="App-header">
<button id="backButton" onClick={backButtonClicked}>Go Back</button>
<h4>Login with your customer account</h4>
<br />
<input id="customerLoginUsername" placeholder=" Username"></input>
<input id="customerLoginPassword" type="password" placeholder=" Password"></input>
<br />
<button id="customerLoginButton" onClick={handleSubmit}>
Login
</button>
<div className="content"></div>
</div>
);
}
export default withRouter(CustomerLoginPage);
By the time the user clicks on the button and your handleSubmit fires, the React component has long since finished rendering, so returning a redirect JSX from the handler won't really have any effect.
If your app architecture supports it (i.e. this isn't an SPA) consider just doing a plain old window.location.reload to send the user to the right place.
If not (and I guess you said this is an SPA), you will have to be clever about how to get the <Redirect> to get acted upon in the next render. React state might be a good start here... in your else clause, instead of returning the redirect, set some state variable that your render method could check for, and render the <Redirect> instead. (Your component is guaranteed to re-render if you change any of its state.)
In other words, modify your render method to either return what it already does, or a <Redirect>, depending on the value of a state variable. You can initialize the state to null and then have your handleSubmit method alter it to, say, the URL you want to redirect to. Then the flow is: render the login page, user provides bad data, your handleSubmit sets state saying so, you re-render now as a <Redirect> component.
Issue
You can't return JSX from an asynchronous callback and expect it to be rendered and take effect. The Redirect must be rendered in the return of the component.
The reason using the history object wasn't working is because the handleSubmit handler is outside the router providing any routing context to navigate against.
Solution
Use the history object to issue an imperative redirect.
Move the HashRouter to wrap App and provide a routing context for the history object. In fact, you should have only one router wrapping your app to provide the routing context, so remove all other extraneous routers you may have in your app.
Example:
index.js
import { HashRouter as Router } from 'react-router-dom';
...
ReactDOM.render(
<React.StrictMode>
<Router>
<App/>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
CustomerHomePage
handleSubmit() {
const usernameInput = document.getElementById("customerLoginUsername");
const passwordInput = document.getElementById("customerLoginPassword");
if (usernameInput.value !== "customer" || passwordInput.value !== "customer") {
// alert("Wrong username or password!");
} else {
this.props.history.replace('/customerhomepage');
}
}
Since you are using react-router I suggest you use the history api to push to the next route.
You can use the withRouter HOC to provide the history api to your props.
import { withRouter } from "react-router";
handleSubmit() {
let usernameInput = document.getElementById('customerLoginUsername');
let passwordInput = document.getElementById('customerLoginPassword');
if (usernameInput.value !== 'customer' || passwordInput.value !== 'customer') {
// alert("Wrong username or password!");
} else {
this.props.history.push('/customerhomepage')
}
}
render() {
return (
<HashRouter>
<Switch>
<Route exact path="/main" component={Main}></Route>
<Route exact path="/customerhomepage" component={CustomerHomePage}></Route>
</Switch>
<div class="App-header">
<NavLink to="/main">
<button id="backButton">Go Back</button>
</NavLink>
<h4>Login with your customer account</h4>
<br />
<input id="customerLoginUsername" placeholder=" Username"></input>
<input id="customerLoginPassword" type="password" placeholder=" Password"></input>
<br />
<button id="customerLoginButton" onClick={this.handleSubmit}>
Login
</button>
<div className="content"></div>
</div>
</HashRouter>
);
}
export default withRouter(CustomerLoginPage);
Edit:
As other have pointed out, you will need to raise the HashRouter component to a higher component, in order for withRouter to work.
There is some data that I want to use on the second page am routing to that page using the reach router Link is there a way we can do so?
We can do this way when using reach-router
An object to put on location state.
Page 1(Navigated from):
const NewsFeed = () => (
<div>
<Link
to="photos/123"
state={{ fromFeed: true }}
/>
</div>
)
page 2(Navigated to):
const Photo = ({ location, photoId }) => {
if (location.state.fromFeed) {
return <FromFeedPhoto id={photoId} />
} else {
return <Photo id={photoId} />
}
}
for more details use this documentation https://reach.tech/router/api/Link[][1]
In the following component, if I click on any of the two buttons, the URL in the address-bar gets changed.
In the list view if you click on the button Details the page get rendered and shows that particular item and the URL in the address bar get changed too.
And in the user view if you click on the "Back to overview" button, the page renders back to the list view and the URL gets changed again.
import React, { useState, useEffect, Fragment } from 'react'
import axios from 'axios'
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const renderItem = (item, userId) => {
const setURL = (e) => {
window.history.pushState(null, null, '/' + e)
setID(item.login.uuid)
}
const clearURL = (e) => {
window.history.back()
setID(null)
}
return (
<Fragment key={item.login.uuid}>
{userId ? (
// User view
<div>
<img src={item.picture.large} />
<h2>
{item.name.first}
</h2>
<p>
{item.phone}
<br />
{item.email}
</p>
<button onClick={() => clearURL('/')}>
Back to overview
</button>
</div>
) : (
// List view
<li>
<img src={item.picture.large} />
<h2>
{item.name.first}
</h2>
<button onClick={() => setURL(item.login.uuid)}>
Details
</button>
</li>
)}
</Fragment>
)
}
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <div>{renderItem(user, true)}</div>
} else {
// List view
return (
<>
<ul>
{resources.map(user => renderItem(user, false))}
</ul>
</>
)
}
}
export default UserList
Everything is working fine.
However, the problem with this solution is that on user view, I cannot use the browsers back button to go back to the list view page.
Is there any way I can change this without using React Route?
So what I believe is happening is you are over-writing the 'history' of the browser. The 'history' is pretty much just a stack of paths you've been and when you click the back button it just pops the current path off the stack. I think that when your using 'window.history.pushState(null, null, '/' + e)' its setting the history = to null so there is nothing to go back to. I would recommend using react-router and just pushing a path ie. router.push('/whatever'). There are a bunch of ways to interact with the history through react without using the native history object. If you want to use the native history object https://developer.mozilla.org/en-US/docs/Web/API/History_API
Edit - I apologize your not overwritting the history, but I do still believe that the error is coming from passing a 'null' value as the 'state' param for the pushState method
Using react-router v4 and BrowserRouter I'm calling history.push to navigate to a different path. Unfortunately the url change in the browser but there is no navigation. I'm not sure if composing route is the right way to go or am I missing something?
<Route render={({history}) =>
<div className="nav" onClick={() => {history.push('/new-route');}}><i
className="fa fa-user"/></div>}
/>
You can try using the context api
const Button = (props, context) => (
<button
type='button'
onClick={() => {
// context.history.push === history.push
context.history.push('/new-location')
}}
>
Click Me!
</button>
)
// you need to specify the context type so that it
// is available within the component
Button.contextTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired
})
}