I'm making a login page in react. When the login is done my react needs to redirect to my page FrontFeed. I'am using this.props.history.push('/feed'); to push the page, but when acess the login page the and put my credentials the login redirect to the login with diferent url and i need to make login again, and only the second time the login redirect to the FrontFeed.
When i access the page for the first time
I made a login (The credentials are right) and the page me redirect to the same page but a diferent url /#/ -> /?#/. The funtions are not called because the requisitions aren't made, and the local storage don't save nothing.
But the second time i made login, everything works fine, the requisitions are maden and the local storage is stored, and my page is redirect to http://localhost:3000/?#/feed.
I'm not sure if i'm using the react-router-dom right. But there's something wrong.
import React from 'react';
import './css/App.css';
import MainPage from './pages/MainPage.js';
function App() {
return (
<MainPage/>
);
}
export default App;
import React, { Component } from 'react';
import '../css/MainPage.css';
import FrontFeed from './FrontFeed.js';
import Login from './Login.js';
import {
Route,
NavLink,
HashRouter
} from "react-router-dom";
export default class MainPage extends Component{
componentDidMount() {
var token = localStorage.getItem('Token');
console.log(token);
}
render () {
return (
<HashRouter>
<div className="app">
<div className="content">
<Route exact path="/" component={Login}/>
<Route path="/feed" component={FrontFeed}/>
</div>
</div>
</HashRouter>
);
}
}
import React, { Component } from 'react';
import '../css/Login.css';
import UserService from '../services/UserService'
import {ToastsContainer, ToastsContainerPosition, ToastsStore} from 'react-toasts';
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
user: new UserService(),
password: '',
username: '',
}
}
componentDidMount() {
var token = localStorage.getItem('Token');
if(token) {
this.props.history.push('/feed');
}
}
handleChangeUsername(event) {
this.setState({username: event.target.value});
}
handleChangePassword(event) {
this.setState({password: event.target.value});
}
handleSubmit(event) {
this.state.user.login(this.state.username, this.state.password).then(res => {
if (res.data.super) {
localStorage.setItem('Token', res.data.key);
ToastsStore.success('Login Sucess!');
} else {
ToastsStore.success("You aren't super!");
}
this.props.history.push('/feed');
}).catch(err => {
ToastsStore.success('Login Failed');
})
}
render() {
return (
<div className="loginContent">
<ToastsContainer position={ToastsContainerPosition.BOTTOM_CENTER}
store={ToastsStore}/>
<div className="title">Login to ADM</div>
<div className="credentialsContainer">
<form onSubmit={e => this.handleSubmit(e)}>
<div className="username">
Username:
<div className="inputUsername">
<input type="text"
value={this.state.username}
onChange={e => this.handleChangeUsername(e)} />
</div>
</div>
<div className="password">
Password:
<div className="inputPassword">
<input type="password"
value={this.state.password}
onChange={e => this.handleChangePassword(e)} />
</div>
</div>
<div className="submitButton">
<input type="submit" value="Search" />
</div>
</form>
</div>
</div>
);
}
}
put event.preventDefault(); inside of the handleSubmit function, it prevents the page to reload so this is why your functions are not getting executed on the first time
a typical form will send data to the action with a method
<form action="/action_page.php" method="post">
but since you are using the handleSubmit() and not an action then it will send data to no where (you can read more about forms here https://www.w3schools.com/html/html_forms.asp )
when you submit the first time, you will see in the url the input values that you have in the form (if you put names on them) but since you didn't put names on them in your form thus you don't see them in the url (assuming from the code that you provided above)
event.preventDefault() prevents the usual form submit and the page reload so you can execute the functions inside of it (same thing I have did to a project I worked on)
after you put event.preventDefault(); to the form, the login will work from the first attempt and react router will redirect it to the /feed page
hope my answer helped a bit
Related
What I want:
Create 2 page in Next.JS in React.JS that can do the following:
User lands in the page 1 (/home) where there is a search bar
User type in the page 1 type what he wants in the search bar and validate the form
User is redirected to the page 2 (/search/:parameter) with the query string in the URL parameter...
Make an HTTP request into the page 2 with the arg passed
Show to the user the result of the http request
What I tried to do and I didn't successfully to do that
import Head from 'next/head'
import React, { Component } from 'react'
import { ApolloClient, InMemoryCache, gql } from '#apollo/client';
import Link from 'next/link'
function submitForm() {
if(process.browser) {
alert(document.getElementById('subvalue').value)
}
}
export default class Home extends React.Component {
render() {
return (
<div className="container">
<Head>
<title>Reddit Feed App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<form action="#" onSubmit={this.handleSubmit}>
<label>Search for any subreddit :</label>
<input type="text" id="subvalue"/>
<span onClick={submitForm()} >Submit</span>
</form>
</main>
...
</div>
)
}
}
So, I'm stuck I'm blocked I already did not succeed to make my submit form function working. How to do it ?
First you have to get the value of the input on submit and programatically changing the current route :
You can get the value by making a ref to the <input /> element :
Your first page should look like this /pages/index.js
import { useRouter } from "next/router";
import { useRef } from "react";
export default function IndexPage() {
const router = useRouter();
const inputRef = useRef(null);
const onSubmit = (e) => {
e.preventDefault();
// some validations here
router.push(`search/${inputRef.current.value}`);
};
return (
<form onSubmit={onSubmit}>
<input name="second_page" type="number" ref={inputRef} />
<button type="submit">submit</button>
</form>
);
}
This will get you to /search/[the value you submitted].
In order to create that dynamic page you should use the Dynamic API Routes.
Just create a file named /pages/[paramPassed].js
I made an example with the jsonplaceholder api :
export default function Search({ res }) {
return (
<div>
Here's the result :
<pre>
<code>{JSON.stringify(res)}</code>
</pre>
</div>
);
}
export async function getServerSideProps({ params }) {
const { paramPassed } = params;
const req = await fetch(
`https://jsonplaceholder.typicode.com/todos/${paramPassed}`
);
const res = await req.json();
return {
props: { res } // will be passed to the page component as props
};
}
The getServerSideProps allows you to set default params to your page component before rendering it.
You can use the fetch API or SWR inside your React components for remote data fetching; or use Next.js' data fetching methods for initial data population.
I build a little React Routing application and wanted to test forwarding to a different react page after a successful HTTP response from a form submit.
When clicking on the submit button the submit confirmation screen in red will be shown, however, shortly afterwards the browser will be forwarded back to the original screen with the form parameters added "/?dummy=dummy". If I do the redirect outside of the promise e.g. before the HTTP call the submit confirmation screen will stay.
Are there useful alternatives to achieve a similar behavior?
Routing setup:
export default function App() {
return (<Router>
<Switch>
<Route path="/" exact component={TestForm} />
<Route path="/submitted" component={Submitted} />
</Switch>
</Router>
);}
The testing component:
class TestForm extends Component {
submitHandler = () => {
let history = this.props.history;
fetch("https://reqres.in/api/users/6")
.then(response => {
history.push({ pathname: "/submitted" });
});
};
render() {
return (<div>
<form onSubmit={this.submitHandler}>
<input type="text" name="dummy" value="dummy" readOnly /><input type="submit" />
</form>
</div>);}}
export default withRouter(TestForm);
Submit confirmation component:
const submitted = props => {
let style = { "background-color": "red" };
return <div style={style}>Form has been submitted</div>;
};
The full code and behavior can be tested in a sandbox.
It is because you do not prevent the default function of the form submit event and it is posting the results.
Update your event handler to prevent the default.
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
class TestForm extends Component {
submitHandler = (e) => {
e.preventDefault()
let history = this.props.history;
fetch("https://reqres.in/api/users/6")
.then(response => {
history.push({ pathname: "/submitted" });
})
.catch(myError => {
console.log(myError);
});
};
render() {
return (
<div>
<form onSubmit={this.submitHandler}>
<input type="text" name="dummy" value="dummy" readOnly />
<input type="submit" />
</form>
</div>
);
}
}
export default withRouter(TestForm);
How I can render another component when i logout on firebase.
I´m trying to re-render the page to print the LoginPage
This is my LoginPage that is render when I loggin with another form.
import React, { Component } from "react";
/*Importing firebase*/
import firebase from "firebase";
/*Importing pages*/
import LoginPage from "../login/LoginPage";
/*Importing components*/
import Header from "./containers/HandleHeader";
class IndexPage extends Component {
shouldComponentUpdate() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
this.forceUpdate();
return <LoginPage />;
}
});
}
render() {
return (
<div>
<Header />
</div>
);
}
}
export default IndexPage;
And this is my handleLogout that work when I click my logout button.
handleLogout = e => {
firebase
.auth()
.signOut()
.then(() => this.forceUpdate());
};
I want to make that when I logout I don´t need reload the page.
Usually the best way to do this is to maintain the logged-in state somewhere, then protect the entry points to any components that require authentication with logic like this:
render() {
const { loggedIn } = this.props;
if (!loggedIn) return <Redirect to="/login" />;
// Reset of component rendered below this point
}
Note that this logic can be in the component itself, some parent component, or some higher order component. The key is to have it somewhere that will prevent access to any protected component by redirecting in the render method before any protected information can be reached.
Redirecting is often achieved using some routing package like, say, react-router-dom, to navigate around. This means that when you log out, a user is implicitly always redirected because they can no longer access the protected components anymore.
This is a basic header component that includes buttons to show login/register forms on click.
The intention is, of course, that the login and register components shouldn't be loaded until requested.
react-loadable is creating an additional file ( 0.js ) that seem to include the register component, but my search did not turn out any reference to login.
In any case, upon initial load, both login and register are being loaded, as my console.log shows.
Of course, I was expecting that they would not be loaded until the triggering button was clicked on.
Note that I did attempt to use react-loadable on routes, and it seems to work correctly, ie, I can see the files being loaded on the network tab of the dev tools.
Also, I happen to have a service worker precaching the build files for now, but I do not believe that should impact this. Or should it? It doesn't on routes.
Excerpts of the header component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withRouter } from 'react-router-dom';
import Loadable from 'react-loadable';
//import Register from '../register/'; ** PREVIOUS REGULAR IMPORT
//import Login from '../login/'; ** PREVIOUS REGULAR IMPORT
import { Loading } from '../../../tools/functions';
const Login = Loadable({
loader: () => import('../login/'),
loading: Loading,
delay: 200,
timeout: 5000
});
const Register = Loadable({
loader: () => import('../register/'),
loading: Loading,
delay: 200,
timeout: 10000
});
render () {
return (
<header>
<div className="header_top flex">
<div className="branding"></div>
<div className="header_spacer"></div>
<Status handleClick={this.handleLoginRegisterClick}/>
</div>
<Nav />
<div className="auth">
<Register
active={this.state.activeRegister}
handleClick={this.handleLoginRegisterClick}
toggleDialog={this.toggleLoginRegisterDialogs}
/>
<Login
active={this.state.activeLogin}
handleClick={this.handleLoginRegisterClick}
handleClickPasswordReset={this.togglePasswordResetRequest}
toggleDialog={this.toggleLoginRegisterDialogs}
/>
<PasswordResetRequest
active={this.state.activePasswordResetRequest}
hidePasswordResetRequest={this.togglePasswordResetRequest}
/>
<SessionHandler location={location}/>
</div>
{showAdForm()}
</header>
);
}
The Loading function:
export const Loading = ({ error }) => {
if (error) {
return 'error';
} else {
return <h3>Loading...</h3>;
}
}
My mistake was that I had the state change on the child component.
So, I factored it up, changing the render method of the header component as follows:
const activeDialog = () => {
if (this.state.activeLogin) {
return (
<Login
active={this.state.activeLogin}
handleClick={this.handleLoginRegisterClick}
handleClickPasswordReset={this.togglePasswordResetRequest}
toggleDialog={this.toggleLoginRegisterDialogs}
/>
)
} else if (this.state.activeRegister) {
return (
<Register
active={this.state.activeRegister}
handleClick={this.handleLoginRegisterClick}
toggleDialog={this.toggleLoginRegisterDialogs}
/>
)
} else if (this.state.activePasswordResetRequest) {
return (
<PasswordResetRequest
active={this.state.activePasswordResetRequest}
hidePasswordResetRequest={this.togglePasswordResetRequest}
/>
)
}
}
return (
<header>
<div className="header_top flex">
<div className="branding"></div>
<div className="header_spacer"></div>
<Status handleClick={this.handleLoginRegisterClick}/>
</div>
<Nav />
<div className="auth">
{activeDialog()}
<SessionHandler location={location}/>
</div>
{showAdForm()}
</header>
);
Good afternoon,
I am having some difficulty working with React and Redux when I am trying to redirect users of my app based on changes in state.
At a high level: I want my app's route to change when my user object in state is populated with information from home / to /student/:username.
Right now I have accomplished this in a hacky sort of fashion.
In my Login component I use the componentDidUpdate lifecycle hook to listen and dispatch an action when an access token from a 3rd party API is passed back to the client from my Express server.
import React from "react";
import LoginForm from "../minor/LoginForm";
const Login = React.createClass({
componentDidUpdate(){
const {success, access_token} = this.props.loginStatus;
if (success === true && access_token !== null){
console.log("It worked getting your data now...");
this.props.getUserData(access_token);
} else if (success === false || access_token === null){
console.log("Something went wrong...");
}
},
render(){
return(
<div className="loginComponentWrapper">
<h1>Slots</h1>
<LoginForm loginSubmit={this.props.loginSubmit}
router={this.props.router}
user={this.props.user} />
New User?
</div>
)
}
});
Notice that I am passing router and user to my LoginForm component. I do this in order to use ANOTHER componentDidUpdate where I use the .push method on router like so:
import React from "react";
const LoginForm = React.createClass({
componentDidUpdate(){
const {router, user} = this.props;
if (user.username !== null){
router.push(`/student/${user.username}`);
}
},
render(){
return(
<div className="loginWrapper">
<div className="loginBox">
<form className="loginForm" action="">
<input ref={(input) => this.username_field = input} type="text" placeholder="username" defaultValue="kitties#kit.com" />
<input ref={(input) => this.password_field = input} type="text" placeholder="password" defaultValue="meowMeow3" />
<button onClick={this.loginAttempt}>Login</button>
</form>
</div>
</div>
)
}
});
Sure it works but I'm certain this is a overly complicated solution and not what is considered a best practice. I've tried adding a custom listener method within my Login component but I've never had it successfully fire off, instead my app gets stuck in a loop.
Is there a way I can do this using Redux and keep my components tidier, or take advantage of componentDidUpdate in a more efficient way?
As always I'd appreciate some more experienced eyes on my issue and look forward to some feedback!
Thanks
UPDATE
App.js
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import * as actionCreators from "../actions/userActions.js";
import StoreShell from "./StoreShell.js";
function mapStateToProps(state){
return{
loginStatus: state.loginStatus,
user: state.user
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch)
}
const App = connect(mapStateToProps, mapDispatchToProps)(StoreShell);
export default App;
This component "sprinkles" all my redux stuff and state data into my container component named StoreShell that in turn passes all that data to props for the elements that make up the UI like Login and LoginForm am I taking too many steps?
StoreShell.js
import React from "react";
const StoreShell = React.createClass({
render(){
return(
<div className="theBigWrapper">
{React.cloneElement(this.props.children, this.props)}
</div>
)
}
})
export default StoreShell;
There are several things that could make the login flow easier to manage and reason about and tidy up your components a bit. First a few general points:
I'm not certain why you have divided login logic between the two components? The idiomatic React/Redux approach would be to have a container component that deals with logic, and a presentation component that deals with presentation. Currently both components do a little bit of each.
You don't need to pass props.router up and down through your components. React router provides a HOC that provides router as a props called withRouter( docs here). You just wrap a component in withRouter and props.router is available - everything else stays the same.
export default withRouter(LoginForm);
My personal feeling is that the URI is part of your app's state, and as such it should be maintained within the store and updated by dispatching actions. There is a cracking library available to do this - react-router-redux. Once you have it setup then you can pass the push method to your components (or elsewhere... see the next points) to navigate:
import { push } from 'react-router-redux';
import { connect } from 'react-redux';
const NavigatingComponent = props => (
<button onClick={() => props.push('/page')}>Navigate</button>
);
const mapStateToProps = null;
const mapDispatchToProps = {
push
};
export default connect(mapStateToProps, mapDispatchToProps)(NavigatingComponent);
One of the great things about having the URI in our store is that we don't need access to props.router to change location, which opens the avenue of moving the login logic outside of our components. There are several ways we can do this, the simplest is probably redux-thunk, which allows our action creators to return functions instead of objects. We could then write our component to simply call a login function with username and password entered, and our thunk takes care of the rest:
import { push } from 'react-router-redux';
// action creators....
const loginStarted = () => ({
type: 'LOGIN_STARTED'
)};
const loginFailed = (error) => ({
type: 'LOGIN_FAILED',
payload: {
error
}
};
const loginSucceeded = (user) => ({
type: 'LOGIN_SUCCEEDED',
payload: {
user
}
};
const getUserData = (access_token) => (dispatch) => {
Api.getUserData(access_token) // however you get user data
.then(response => {
dispatch(loginSucceeded(response.user));
dispatch(push(`/student/${response.user.username}`));
});
export const login = (username, password) => (dispatch) => {
dispatch(loginStarted());
Api.login({ username, password }) // however you call your backend
.then(response => {
if (response.success && response.access_token) {
getUserData(access_token)(dispatch);
} else {
dispatch(loginFailed(response.error));
}
});
}
The only thing your components do is call the initial login function, which could be implemented something like:
Container:
import React from 'react';
import { connect } from 'react-redux';
import { login } from '../path/to/actionCreators';
import LoginForm from './LoginForm';
const LoginContainer = React.createClass({
handleSubmit() {
this.props.login(
this.usernameInput.value,
this.passwordInput.value
);
},
setUsernameRef(input) {
this.usernameInput = input;
},
setPasswordRef(input) {
this.passwordInput = input;
},
render() {
return (
<LoginForm
handleSubmit={this.handleSubmit.bind(this)}
setUsernameRef={this.setUsernameRef.bind(this)}
setPasswordRef={this.setPasswordRef.bind(this)}
/>
);
}
});
const mapStateToProps = null;
const mapDispatchToProps = {
login
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer);
Component:
import React from 'react';
export const LoginForm = ({ handleSubmit, setUsernameRef, setPasswordRef }) => (
<div className="loginWrapper">
<div className="loginBox">
<form className="loginForm" action="">
<input ref={setUsernameRef} type="text" placeholder="username" defaultValue="kitties#kit.com" />
<input ref={setPasswordRef} type="text" placeholder="password" defaultValue="meowMeow3" />
<button onClick={handleSubmit}>Login</button>
</form>
</div>
</div>
);
This has achieved separation of a logic/data providing container, and a stateless presentational component. The LoginForm component can be written simply as a function above because it has no responsibility to deal with logic. The container is also a very simple component - the logic has all been isolated in our action creator/thunk and is much easier to read and reason about.
redux-thunk is just one way of managing asynchronous side effects with redux - there are many others with different approaches. My personal preference is toward redux-saga, which may be interesting for you to look at. In my own redux journey, however, I certainly benefited from using and understanding redux-thunk first before finding it's limitations/drawbacks and moving on, and would suggest this route to others.
If you're using react-router version 4.x.x: You can just render a Redirect component that handles the redirection for you (see example in react-router docs).
import React from "react";
import { Redirect } from "react-router";
import LoginForm from "../minor/LoginForm";
const Login = React.createClass({
componentDidUpdate(){
const {success, access_token} = this.props.loginStatus;
if (success === true && access_token !== null){
console.log("It worked getting your data now...");
this.props.getUserData(access_token);
} else if (success === false || access_token === null){
console.log("Something went wrong...");
}
}
render(){
// if user is defined, redirect to /student/:username
if (this.props.user.username !== null) {
return (<Redirect to={ `/student/${this.props.user.username}` } />)
}
// otherwise render the login form
return(
<div className="loginComponentWrapper">
<h1>Slots</h1>
<LoginForm loginSubmit={this.props.loginSubmit}
router={this.props.router}
user={this.props.user} />
New User?
</div>
)
}
});
If you're using react-router version 3.x.x: The the way you're doing it is mostly correct. I would move the redirect from the LoginForm component to Login component; that way LoginForm does not need to depend on the user prop.
I know, I don't like the pattern much either.
Hope this helps!