React Router: How to keep logged in data (props) when re-routing - javascript

I'm creating a React Router app and I'm learning authentication. Here are some parts:
Login component (login.jsx)
authenticates user's credential on the back end
sets the authentication token in local storage
return's the user's data
sets state with the data
passes state to Admin component
Admin component (admin.jsx)
protected by a private route (see privateroute.jsx)
basically a container component
passes user's data to and renders other components that display/edit the data
Authentication Button on the nav bar (authbutton.jsx)
checks if user is logged in and renders "login" or "logout" button
if logged in, also renders "my posts" button that routes to Admin
All works well from Login to Admin. My problem is that when I click away from the Admin page (like the home page) and then click on "my posts" button, it reroutes to Admin and knows I'm logged in, but the user's data is no longer available. Before, coming from the login component, the user's data was in this.props.location.state.me.
I'm stuck because I'm trying to route to Admin from two different components and I've never done that before. Furthermore, I feel like there's a solution in the authentication setup that I'm missing.
Other ideas:
Should I conditionally set state in Admin when the user's data is passed?
Should I store the data in local storage in the browser like I'm doing with the authentication token?
I tried fetching data, setting state in Admin with componentDidMount but it didn't re-render so I read to use componentWillReceiveProps but that's being deprecated and replaced with getDerivedStateFromProps. Could not figure that out.
login.jsx
import React, { Component, Fragment } from 'react';
import * as userService from '../../services/user';
import { Redirect } from 'react-router-dom';
import IndeterminateProgress from '../utilities/indeterminateprogress';
import Nav from '../home/nav';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
redirectToReferrer: false,
email: '',
password: '',
feedbackMessage: '',
checkingLogin: true,
me: ''
};
}
componentDidMount() {
userService.checkLogin()
.then((loggedIn) => {
if (loggedIn) {
this.setState({ redirectToReferrer: true, checkingLogin: false });
} else {
this.setState({ checkingLogin: false });
}
});
}
login(e) {
e.preventDefault();
userService.login(this.state.email, this.state.password)
.then((meData) => {
this.setState({ redirectToReferrer: true, me: meData })
})
.catch((err) => {
if (err.message) {
this.setState({ feedbackMessage: err.message });
}
});
}
handleEmailChange(value) {
this.setState({ email: value });
}
handlePasswordChange(value) {
this.setState({ password: value });
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/admin', state: { ...this.state } } };
const { redirectToReferrer, checkingLogin } = this.state;
if (checkingLogin) {
return <IndeterminateProgress message="Checking Login Status..." />;
}
if (redirectToReferrer) {
return (
<Redirect to={from} />
);
}
return (
<Fragment>
<Nav />
<h2 className="heading center">Login to continue</h2>
<form className="center" onSubmit={(e) => this.login(e)}>
<div className="form-group">
<input
placeholder="Email"
id="email"
className="col-3"
type="email"
onChange={(e) => this.handleEmailChange(e.target.value)}
required
/>
</div>
<div className="form-group">
<input
placeholder="Password"
id="password"
className="col-3"
type="password"
onChange={(e) => this.handlePasswordChange(e.target.value)}
required
/>
</div>
{this.state.feedbackMessage ? (
<p>{this.state.feedbackMessage}</p>
) : null}
<input type="submit" value="Login" className="btn btn-info btn-sm" />
</form>
</Fragment>
);
}
}
export { Login };
admin.jsx
import React, { Component } from 'react';
import Nav from '../home/nav';
import AdminBlogContainer from './adminblogcontainer'
import { BrowserRouter as Router, Link } from 'react-router-dom';
const Admin = (props) => {
return (
<div className="flexcol center">
<Nav />
<h1 className="heading">Your Blog Posts</h1>
<AdminBlogContainer {...props.location.state.me} />
<Link to={{
pathname: '/write',
state: { ...props.location.state.me }
}}
className="btn btn-outline-secondary mt-4"
>Create a New Blog Post</Link>
</div>
)
}
export { Admin };
privateroute.jsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isLoggedIn } from '../../services/user';
const PrivateRoute = (props) => {
const Component = props.component;
const propsToPass = Object.assign({}, props);
delete propsToPass.component;
return (
<Route {...propsToPass} render={props => (
isLoggedIn() ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)
)} />
);
};
export { PrivateRoute }
authbutton.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import { isLoggedIn } from '../../services/user';
const AuthButton = (props) => {
if (isLoggedIn()) {
return (
<div>
<Link className="btn btn-info m-1" to="/logout">Logout</Link>
<Link className='btn btn-info m-1' to={{
pathname: '/admin',
// state: { ...this.state }
}}
>My Posts</Link>
</div>
);
} else {
return (
<div>
<Link className="btn btn-info m-1" to="/login">Login</Link>
<Link className="btn btn-info m-1" to="/register">Register</Link>
</div>
)
}
};
export { AuthButton };

This is one of the reasons as to the creation of stores. Think of it as a global object that can be accessed anywhere within your application.
I personally have used mobx/mobx-react (easy and does things magically) and there is also redux
Using mobx you can do something like so:
Global store
// /stores/authentication.js
class AuthenticationStore {
user = {};
//authentication logic here
}
const authenticationStore = new AuthenticationStore();
export default authenticationStore;
Root App Component
// /app.js
import authenticationStore from './stores/authentication';
import { Provider } from 'mobx-react';
export default class App extends Component {
render(){
return (
<Provider authenticationStore={authenticationStore}>
<BrowserRouter>
<SomeComponent/>
</BrowserRouter>
<Provider>);
}
}
Some component;
// /components/some.component.js
#inject('authenticationStore')
class SomeComponent extends Component {
render(){
const {authenticationStore} = this.props;
const {user} = authenticationStore;
render(
<div>${user.name}</div>
)
}
}

There seem to be two main solutions to my problem. 1 is to use a store as answered on this page. The other is to store the data in local storage, which looks like:
in login.jsx set local storage instead of setting state
login(e) {
e.preventDefault();
userService.login(this.state.email, this.state.password)
.then((meData) => {
localStorage.setItem("me", JSON.stringify(meData))
this.setState({ redirectToReferrer: true})
})
.catch((err) => {
if (err.message) {
this.setState({ feedbackMessage: err.message });
}
});
}
retrieve data in adminblogcontainer.jsx
componentDidMount() {
let meData = JSON.parse((localStorage.getItem("me")))
authorsService.one(meData.id) ...

Related

(React) How to Redirect to Result page on Search

So im working on some project school and im stuck on building search bar so when i enter it doesnt want to go to my result page instead it goes to http://localhost:3000/Masing? heres the code
import { ReactSearchAutocomplete } from 'react-search-autocomplete';
import { Redirect } from "react-router-dom";
import React, { Component } from 'react';
class Search extends Component {
state = {
query: '',
results: []
};
render() {
const { results } = this.state
const { props } = this.props
return (results.length > 0 ? <Redirect
to={{
pathname: '/Kerjasamaln',
state: {
results: results,
from: props.location
}
}}
/> : <form>
<ReactSearchAutocomplete
className='search'
type='text'
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
</form>
)
}
}
export default Search;
any help i'd greatly appreciate it

React: How to redirect

I am a beginner in React and was implementing a function where on a button click in the render method, I go to a function foo. In that function, I am sending the username and password to a server.
If the username and password are correct, it returns a JSON object like
{"Result":1,"Cookie":"COOKIE!!!"}
I am trying to redirect it to another class component I have made (Flood) if result is 1. Can someone kindly help me
I tried redirecting it after render and before return but I get an error
Error: Invariant failed: You should not use <Redirect> outside a <Router>
import React from 'react';
import './style.scss';
import LoginImage from './LoginImage.png'
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
import {Redirect, Router} from 'react-router-dom'
//import Logfailed from './Logfailed'
import Flood from './Flood'
class UserLogin extends React.Component {
constructor(props) {
super(props);
this.state = {userName:'', password:'', act:'l', flag:0, txt:''};
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.handleClick = this.handleClick.bind(this);
}
async handleClick(e) {
const url = 'http://52.8.557.164/user'
const data = {username:this.state.userName, password:this.state.password, action:this.state.act};
try {
const response = await fetch(url,
{
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
});
const json = await response.json();
if(json['Result'] === 1) {
this.setState({flag: 1, txt:''});
}
else {
this.setState({flag:2, txt:'Wrong username and Password'});
}
console.log('Success', JSON.stringify(json));
console.log(json['Cookie']);
} catch (error) {
console.error('Error', error);
}
}
handleChange1(e) {
this.setState({userName: e.target.value})
}
handleChange2(e) {
this.setState({password: e.target.value})
}
render() {
if (this.state.flag === 1) {
return <Redirect to='/Flood' />
}
return (
<div className = 'outer-container' ref={this.props.containerRef}>
<div className = 'header'> Login </div>
<div className="content">
<div className="image">
<img src={LoginImage} />
</div>
<Form className = 'form'>
<Form.Group controlId="formBasicEmail" className = 'form-group'>
<Form.Label style={{marginTop: '90px'}}>Username</Form.Label>
<Form.Text className="text-muted" htmlFor="username"></Form.Text>
<input type="text" value = {this.state.userName} name="username" placeholder="username" onChange={this.handleChange1}/>
</Form.Group>
<Form.Group controlId="formBasicPassword" className = 'form-group'>
<Form.Label>Password</Form.Label>
<Form.Text className="text-muted" htmlFor="password"></Form.Text>
<input type="password" value = {this.state.password} name="password" placeholder="password" onChange={this.handleChange2} />
<br></br>
<span>{this.state.txt}</span>
</Form.Group>
</Form>
</div>
<div className="footer">
<Button variant="outline-primary" size="lg" onClick={this.handleClick} className="btn" block>
Login
</Button>
</div>
</div>
);
}
}
export default UserLogin;
import React from 'react';
class Flood extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<h1>gg</h1>
)}
}
export default Flood;
import React from 'react';
import './App.css';
import UserLogin from './UserLogin';
import Register from './Register'
import { Router, Redirect} from 'react-router-dom'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
login: true
};
}
componentDidMount() {
this.rightSide.classList.add("right");
}
changeState() {
const { login } = this.state;
if (login) {
this.rightSide.classList.remove("right");
this.rightSide.classList.add("left");
} else {
this.rightSide.classList.remove("left");
this.rightSide.classList.add("right");
}
this.setState(prevState => ({ login: !prevState.login }));
}
render() {
const {login} = this.state;
const curr = login ? "Register" : "Login";
const currentActive = login ? "login" : "register";
return (
<div className="App">
<div className="login">
<div className="container" ref={ref => (this.container = ref)}>
{login && (
<UserLogin containerRef={ref => (this.curr = ref)} />
)}
{!login && (
<Register containerRef={ref => (this.curr = ref)} />
)}
</div>
<RightSide
curr={curr}
currentActive={currentActive}
containerRef={ref => (this.rightSide = ref)}
onClick={this.changeState.bind(this)}
/>
</div>
</div>
);
}
}
const RightSide = props => {
return (
<div
className="right-side"
ref={props.containerRef}
onClick={props.onClick}
>
<div className="inner-container">
<div className="text">{props.curr}</div>
</div>
</div>
);
};
export default App;
BrowserRouter is the provider to be used in React Router for usage of anything related to routing. To add it to your component:
import { BrowserRouter as Router } from "react-router-dom";
class App extends React.Component {
render() {
return (
<Router>
// Rest of the App component here.
</Router>
);
}
}
Note that there needs to be only one wrapping Router in an application (generally) and hence it makes sense to wrap the entry component in it.
Basic Routing Example - React Routing
First of all, you need to wrap your component using withRouter tag
import { withRouter } from 'react-router-dom'
then wrap your component/class when you're exporting
export default withRouter(yourComponent);
ok, now back to the issue:
To redirect, you can simply push something to the history object
history.push('/redirect-location');

How to hide some component based on some flag in react js

I want to hide some component based on some flag in react js.
I have an App component where I have Login and other components, I want to hide the other component until Login components this.state.success is false and on click of a button I am changing the sate, but it's not working, I am new to react,
My App Class compoenent -
import React, { Component } from "react";
import logo from "../../logo.svg";
// import Game from "../Game/Game";
import Table from "../Table/Table";
import Form from "../Table/Form";
import Clock from "../Clock/Clock";
import "./App.css";
import Login from "../Login/Login";
class App extends Component {
state = {
success: false
};
removeCharacter = index => {
const { characters } = this.state;
this.setState({
characters: characters.filter((character, i) => {
return i !== index;
})
});
};
handleSubmit = character => {
this.setState({ characters: [...this.state.characters, character] });
};
handleSuccess() {
this.setState({ success: true });
}
render() {
const { characters, success } = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<span className="Span-inline">App</span>
<Clock time={new Date()} />
</header>
<Login success={success} handleSuccess={this.handleSuccess} />
{success && (
<div className="container">
<h1>React Tutorial</h1>
<p>Add a character with a name and a job to the table.</p>
<Table
characterData={characters}
removeCharacter={this.removeCharacter}
/>
<h3>Add New character</h3>
<Form handleSubmit={this.handleSubmit} />
</div>
)}
{/* <Game /> */}
</div>
);
}
}
export default App;
My Login component -
import React, { Component } from "react";
import Greeting from "./Greeting";
import LogoutButton from "./LogoutButton";
import LoginButton from "./LoginButton";
class Login extends Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn: false,
name: "",
success: false
};
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
this.setState({ success: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
this.setState({ success: false });
}
onChange = e => {
this.setState({
name: e.target.value
});
};
render() {
const isLoggedIn = this.state.isLoggedIn;
const name = this.state.name;
// const successLogin = this.state.success;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting
isLoggedIn={isLoggedIn}
name={name}
onChange={this.onChange}
/>
{button}
</div>
);
}
}
export default Login;
please guide me on what I am doing wrong.
Why sometime debuggers do not trigger in react component?
For the sake of example I have used functional stateless component here. You can use Class component all upto you.
const conditionalComponent = (props) => {
let condition = true;
return (
{condition && <div><h1>Hello world</h1></div>}
}
Instead of directly giving condition you can even call function which returns a boolean value.
handleLoginClick() {
this.setState({ isLoggedIn: true });
this.setState({ success: true });
this.props.handleSuccess()
}
do like this
<Login success={success} handleSuccess=
{this.handleSuccess} />
bind this function

Fetch and display new contents when user click on each list item [React]

First, I want to load a JSON from my server; it will contain a list of objects. Then, I need to render these objects like a list. When a list item is clicked, the user should be redirected to a page that shows information about the clicked item. That information displayed should be fetched from another API call to my server.
Can someone guide me, please?
state = {
isLoading: true,
users: [],
error: null
};
fetchUsers() {
fetch(`http://localhost:3001/blog/view`)
.then(response => response.json())
.then(data =>
this.setState({
users: data,
isLoading: false,
})
)
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.fetchUsers();
}
render() {
const { isLoading, users, error } = this.state;
return (
<React.Fragment>
<h1 style={{textAlign: 'center'}}>My Blog</h1>
{error ? <p>{error.message}</p> : null}
{!isLoading ? (
users.map(user => {
const { _id, title, details,date } = user;
return (
<div className='blog'>
<div key={_id}>
<p>Name: {title}</p>
<p>Email Address: {details}</p>
<p >Email Address: {date}</p>
<hr className='banner-text hr' />
</div>
</div>
);
})
) : (
<h3>Loading...</h3>
)}
</React.Fragment>
);
}
}
Here is a sample project to demonstrate how you can use react and react-router together.
We first fetch a list of users from some api, and then display them as a list.
Using react-router, we add a link to each item so that when it's clicked, the page url changes
but page wont reload! these are internal links
Then again using react-router, we display different contents based on the url.
And at last, we have a UserPage component that when mounted, fetches the data for the specific user page and renders it.
Hope it is clear enough
This is a very good tutorial on react-router
And this is the official react tutorial
I strongly recommend that you take a look at them
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js
import React, { Component } from 'react'
import {BrowserRouter as Router, Link, Route} from 'react-router-dom'
import User from "./User"
import UserPage from "./UserPage"
class App extends Component {
constructor() {
super()
this.state = {
users: [],
isLoading: false
}
}
componentDidMount() {
this.setState({ isLoading: true })
fetch('https://your/api/url')
.then(response => response.json())
.then(response => {
this.setState({
users: response,
isLoading: false
})
})
}
render() {
let content;
if (this.state.isLoading) {
content = <h1>Loading...</h1>
} else if (this.state.users.length > 0) {
content = this.state.users.map(u =>
<Link to={`/users/${u._id}`}>
<User key={u._id} user={u} />
</Link>
)
} else {
content = <h4>No users found!</h4>
}
return (
<Router>
<div>
<Route path="/users/:_id" component={UserPage}/>
<Route exact={true} path="/" render={() => content}/>
</div>
</Router>
)
}
}
export default App;
User.js
import React from 'react'
function User(props) {
const {title, details, date} = props.user;
return (
<div>
<p>Name: {title}</p>
<p>Email Address: {details}</p>
<p>Email Address: {date}</p>
<hr className='banner-text hr' />
</div>
)
}
export default User
UserPage.js
import React, {Component} from 'react'
class UserPage extends Component{
constructor(props) {
super(props)
this.state = {
isLoading: false,
data: '',
id: this.props.match.params._id
}
}
componentDidMount() {
this.setState({ isLoading: true })
fetch(`https://your/api/url/for/user/${this.state.id}`)
.then(response => response.json())
.then(response => {
this.setState({
data: response,
isLoading: false
})
})
}
render() {
return (
this.state.isLoading ?
(<h1>Loading page of user {this.state.id}...</h1>)
:
(
<div>
<p>{this.state.data}</p>
</div>
)
)
}
}
export default UserPage

React - How to redirect to another component on Search?

So I'm working on an app for school and I've been stuck on an issue for like two days. I'm building a search that gets data from TMDB, and all that works fine. When I type in the input all the data come flowing in! However, when I submit and try to redirect to the /results page which is linked to a component that displays the SearchResults when directed to, nothing happens and it stays on the homepage... I tried Redirect but either I'm using it incorrectly, or I shouldn't be using it in this case. Here's the code for my Search component, if you could help me out, I'd greatly appreciate it!:
import React, { Component, Redirect } from 'react';
import axios from 'axios';
class Search extends Component {
state = {
query: '',
results: []
};
getInfo = () => {
axios
.get(
`https://api.themoviedb.org/3/search/tv?api_key=6d9a91a4158b0a021d546ccd83d3f52e&language=en-US&query=${
this.state.query
}&page=1`
)
.then(({ data }) => {
this.setState({
results: data
});
});
};
handleInputChange = e => {
e.preventDefault();
this.setState(
{
query: this.search.value
},
() => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
this.getInfo();
}
} else if (!this.state.query) {
}
}
);
};
render() {
return (
<div>
<form>
<input
className='search'
placeholder='⌕'
type='text'
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
</form>
{this.state.results.length > 0 && (
<Redirect
to={{
pathname: '/results',
state: { results: this.state.results }
}}
/>
)}
</div>
);
}
}
export default Search;
You can use withRouter to get history prop injected and then do: history.push(“/results”)
The code will look something like this:
import React, { Component, Redirect } from 'react';
import axios from 'axios';
import { withRouter } from "react-router";
class Search extends Component {
state = {
query: '',
results: []
};
componentDidUpdate(prevProps, prevState) {
const { history } = this.props;
if (prevState.results !== this.state.results) {
history.push('/results');
}
}
getInfo = () => {
axios
.get(
`https://api.themoviedb.org/3/search/tv?api_key=6d9a91a4158b0a021d546ccd83d3f52e&language=en-US&query=${
this.state.query
}&page=1`
)
.then(({ data }) => {
this.setState({
results: data
});
});
};
handleInputChange = e => {
e.preventDefault();
this.setState(
{
query: this.search.value
},
() => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
this.getInfo();
}
} else if (!this.state.query) {
}
}
);
};
render() {
return (
<div>
<form>
<input
className='search'
placeholder='⌕'
type='text'
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
</form>
</div>
);
}
}
export default withRouter(Search);
This way you are programmatically navigating, using push method from react router's history.
Here you can read more about withRouter HOC: https://reacttraining.com/react-router/web/api/withRouter
Are you using React Router for routing?. Do you need to change your URL? There isn't much of a need for something like this. Sending someone to /results is just old school unless you are going to do something like /results?q=pulp&piction so someone can refresh or link directly to the results.
For something like this just display your result component
{this.state.results.length && (
<SearchResults results={this.state.results} />
)}
If you are using a router and need to use the path for the school project school your teacher for dumb business requirements and give us more information about what you are using.
"Redirect" component is inside the "react-router-dom" package.
use this approach:
import { Redirect } from "react-router-dom";
import React, { Component } from 'react';
class Search extends Component {
//...
render() {
const { results } = this.state
const { props } = this.props
return (results.length > 0 ? <Redirect
to={{
pathname: "/results",
state: {
results: results,
from: props.location
}
}}
/> : <form>
<input
className='search'
placeholder='⌕'
type='text'
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
</form>
)
}
}

Categories