How to pass props in the modal in react - reactjs

I am creating my first react project, i am using GitHub api to fetch user and display them firstly in card view then on clicking on more button to any profile i want to create a modal using portals in react till now i am able to create an modal but now i am not getting how to get data to that modal coponent
Here is my App.js
import React, { Fragment, Component } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Users from './components/users/Users';
import User from './components/users/User';
import Modal from './components/Modal/Modal'
import Search from './components/users/Search';
import Alert from './components/layout/Alert';
import About from './components/pages/About';
import axios from 'axios';
import './App.css';
class App extends Component {
state = {
users: [],
user: {},
loading: false,
alert: null,
modal: {},
}
// get users from Search.js
searchUsers = async text => {
this.setState({ loading: true })
const res = await axios.get(
`https://api.github.com/search/users?q=${text}&client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID
}&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`);
this.setState({ users: res.data.items, loading: false })
console.log(text);
}
//get single profile
getUser = async username => {
this.setState({ loading: true })
const res = await axios.get(
`https://api.github.com/users/${username}?client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID
}&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`);
this.setState({ user: res.data, loading: false });
this.setState({ modal: res.data, loadading: false });
}
//clear search
clearUsers = () => this.setState({ users: [], loading: false });
setAlert = (msg, type) => {
this.setState({ alert: { msg: msg, type: type } });
setTimeout(() => this.setState({ alert: null }), 5000);
};
render() {
return (
<Router>
<div className='App'>
<Navbar />
<div className="container">
<Alert alert={this.state.alert} />
<Switch>
<Route exact path='/'
render={props => (
<Fragment>
<Search
searchUsers={this.searchUsers}
clearUsers={this.clearUsers}
showClear={this.state.users.length > 0 ? true : false}
setAlert={this.setAlert}
/>
<Users loading={this.state.loading} users={this.state.users} />
</Fragment>
)} />
<Route path='/about' component={About} />
<Route path='/user/:login' render={props => (
<User {...props} getUser={this.getUser} user={this.state.user} loading={this.state.loading} />
)} />
<Route path='/modal/:login' render={props => (
<Modal {...props} getUser={this.getUser} modal={this.state.modal} loading={this.state.loading} />
)} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;
here is my Modal.js
import React, { Fragment, Component } from 'react';
import ReactDom from 'react-dom';
import Spinner from '../layout/Spinner';
import { Link } from 'react-router-dom';
const modalRoot = document.getElementById('modal');
export default class Modal extends Component {
constructor() {
super();
this.el = document.createElement('div');
}
componentDidMount = () => {
modalRoot.appendChild(this.el);
};
componentWillUnmount = () => {
modalRoot.removeChild(this.el);
};
render() {
const {
children,
name,
avatar_url,
location,
bio,
blog,
followers,
following,
public_repos,
} = this.props.modal;
const { loading } = this.props;
if (loading) return <Spinner />
return (
ReactDom.createPortal(children, this.el)
)
}
}
any guide would be appriciated thanks in advance

You are passing the props already to Modal.
In Modal, do something like
Class Modal extends Component {
constructor(){
super(props);
}
render(){
const {
modal,
getUser,
loading,
anyOtherPropYouPassIn
} = this.props;
const { loading } = this.props;
if (loading) return <Spinner />
return (
ReactDom.createPortal(children, this.el)
)
}

Related

'this.props.displaychange is not a function' when I use Route

*Sorry in advance I couldn't split the code into separate components in the post
I have a question that I will be able to explain well
I have an authentication component that receives a prop from another component
Now when I put all the components under one big parent component that contains all the components everything works fine and the props pass successfully
But as soon as I start using route I get this message
"Uncaught (in promise) TypeError: this.props.displaychange is not a function"
whet I tried to do here is to change the state in the TeamList component (state.displayTeams to true)
import './App.css';
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import Home from './components/Home/Home';
import LogIn from './components/Log-in/Log-in';
import SignIN from './components/Sign-in/Sign-in';
import TeamList from './components/Team-list/Team-list';
import TeamDisplay from './components/TeamDisplay/TeamDisplay';
function App() {
return (
<div>
<Routes>
<Route path='/' exact element={<Home />} />
<Route path='/login' element={<LogIn />} />
<Route path='/sign-in' element={<SignIN />} />
<Route path='/teamlist' element={<TeamList />} />
<Route path='/teamdisplay' element={<TeamDisplay />} />
</Routes>
</div>
);
}
export default App;
import React, { Component } from "react";
import { signInWithEmailAndPassword, signOut } from 'firebase/auth'
import { auth } from '../../firebase-config'
import './Log-in.css'
import { Link } from "react-router-dom";
class LogIn extends Component {
state = {
logEmail: '',
logPass: '',
user: '',
login: false
}
login = async (displaychange, e) => {
e.preventDefault()
this.setState({ login: true })
const user = await signInWithEmailAndPassword(auth, this.state.logEmail, this.state.logPass)
console.log(user.user.email)
this.setState({ user: user.user.email })
this.setState({ logEmail: '', logPass: '' })
console.log('logged in')
this.props.displaychange()
}
logOut = async (displaychangeOut, e) => {
e.preventDefault()
if (this.state.login) {
await signOut(auth)
this.setState({ user: '', logEmail: '', logPass: '' })
console.log('you out')
this.props.displaychangeOut()
} else {
return
}
}
render() {
return (
<div className="Login-form">
<form>
<span>ACCOUNT LOGIN</span>
<label>USERNAME </label>
<input value={this.state.logEmail} onChange={(e) => this.setState({ logEmail: e.target.value })} name="nameLog" placeholder="Your name..." />
<label>PASSWORD </label>
<input value={this.state.logPass} onChange={(e) => this.setState({ logPass: e.target.value })} name="passLog" placeholder="Your password..." />
<button onClick={(e) => this.login(this.props.displaychange, e)}>LOG IN</button>
</form>
</div>
)
}
}
export default LogIn
import React, { Component } from "react";
import { db } from '../../firebase-config'
import { collection, addDoc, getDocs, getDoc, doc } from 'firebase/firestore'
import TeamDisplay from "../TeamDisplay/TeamDisplay";
import LogIn from "../Log-in/Log-in";
const teamCollectionRef = collection(db, 'Teams')
class TeamList extends Component {
state = {
teams: [
],
displayTeams: false
}
componentDidMount() {
getDocs(teamCollectionRef)
.then(snap => {
let teams = []
snap.docs.forEach(doc => {
teams.push({ ...doc.data() })
});
this.setState({ teams: teams });
});
}
changedisplay = () => {
this.setState({ displayTeams: true })
}
changedisplayOut = () => {
this.setState({ displayTeams: false })
}
render() {
let displayTeam
if (this.state.displayTeams) {
displayTeam = this.state.teams.map(item => <TeamDisplay key={item.teamId} team={item} />)
} else {
displayTeam = null
}
return (
<div>
{displayTeam}
</div>
)
}
}
export default TeamList

Can not pass props to component

I am creating an embedded react application into a wordpress site. I have fetched the data to show a list of posts however I can not pass the props to the Single post page. I can only think that it has something to do with the BrowserRouter and react-router-dom. I've searched Stack Overflow and can not find anything that is recent, the things I have tried have all failed. There maybe something very simple that I am missing.
Home.js
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import "../sass/home.scss";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
posts: [],
error: "",
};
}
componentDidMount() {
const wordPressSiteUrl = "http://localhost/wordpress";
this.setState({ loading: true }, () => {
axios
.get(`${wordPressSiteUrl}/wp-json/acf/v3/property`)
.then((res) => {
this.setState({ loading: false, posts: res.data });
console.log(res.data);
})
.catch((error) =>
this.setState({ loading: false, error: error.response.data })
);
});
}
render() {
const { posts } = this.state;
return (
<div>
{posts.length ? (
<div className="post-container">
{posts.map((post) => (
<div key={post.id} className="card">
<div className="card-header">
<Link to={`/property/${post.id}`}>
{post.acf.property_title}
</Link>
</div>
<div className="card-img-top">
<img src={post.acf.featured_image.url} alt="" />
{console.log(post.id)}
</div>
</div>
))}
</div>
) : (
""
)}
</div>
);
}
}
export default Home;
As it stands, I am getting undefined in the console log.
SinglePost.js;
import React from "react";
import axios from "axios";
class SinglePost extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
post: {},
error: "",
};
}
componentDidMount() {
const wordPressSiteUrl = "http://localhost/wordpress";
console.warn(this.props.id);
this.setState({ loading: true }, () => {
axios
.get(`${wordPressSiteUrl}/wp-json/acf/v3/property/${this.props.id}`)
.then((res) => {
this.setState({ loading: false, post: res.data });
})
.catch((error) =>
this.setState({ loading: false, error: error.response.data })
);
// console.log(this.props.id);
});
}
render() {
const { post, error, loading } = this.state;
return (
<div>
{Object.keys(post).length ? (
<div className="post-container">
<div key={post.id} className="card">
<div className="card-header">{post.acf.property_title}</div>
<div className="card-img-top">
<img src={post.acf.featured_image.url} alt="" />
{console.log(post.id)}
</div>
</div>
</div>
) : (
""
)}
</div>
);
}
}
export default SinglePost;
App.js
import React from "react";
import Home from "./components/Home";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import SinglePost from "./components/SinglePost";
import Navbar from "./components/Navbar";
function App() {
return (
<div className="App">
<BrowserRouter>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/property/:id" element={<SinglePost />} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;

Props not rendering

having some trouble rendering props in a component for a project using Reactjs. The information is showing in props in the react dev tools, yet I am unable to render them on the browser. When console logging, there is no value showing...
I'm wondering if I need to dig deeper into the api in order to grab what I need?
CocktailRecipe.js
````import React, { Component } from 'react'
// import Spinner from '../layout/Spinner'
class CocktailRecipe extends Component {
componentDidMount(){
this.props.getCocktail(this.props.match.params.idDrink);
// console.log(this.props.match.params.idDrink)
}
render() {
const {strDrink} = this.props.cocktailrecipe;
console.log(strDrink);
// console.log(this.props.cocktailrecipe.strDrink);
// const {loading} = this.props.loading;
// if (loading) {
// <Spinner />
// }else{
return (
<div>
<h3>{strDrink}</h3>
<h3>This is the title</h3>
</div>
)
// }
}
}
export default CocktailRecipe````
app.js
````import { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import './App.css';
import Navbar from './layout/Navbar';
import CocktailList from './cocktail/CocktailList';
import axios from 'axios';
import Search from './cocktail/Search';
import Alert from './layout/Alert';
import About from './pages/About';
import CocktailRecipe from './cocktail/CocktailRecipe';
class App extends Component {
state={
cocktails: [],
cocktailrecipe:{},
loading: false,
msg:'',
type:''
}
async componentDidMount() {
try {
this.setState({loading: true})
const res = await axios.get('https://www.thecocktaildb.com/api/json/v1/1/search.php?s=');
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
handleSearchCocktails= async (text) => {
try{
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${text}`);
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
// Get cocktail recipe
getCocktail = async (idDrink) => {
try {
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${idDrink}`);
// console.log(res.data.drinks);
this.setState({cocktailrecipe: res.data.drinks.id, loading: false})
} catch(error) {
console.log(error)
}
}
handleClearCocktails= () => {
this.setState({cocktails:[], loading: false})
}
handleSetAlert=(msgfromSearch, typefromSearch)=>{
this.setState({ msg:msgfromSearch, type:typefromSearch })
setTimeout(()=>this.setState({msg:'', type:''}), 5000)
}
render() {
const {cocktails, loading, cocktailrecipe} = this.state;
return (
<Router>
<div className="App">
<Navbar title="COCKTAIL LIBRARY" />
<div className="container">
<Alert msg={this.state.msg} type={this.state.type} />
<Switch>
<Route exact path='/' render={props=>(
<Fragment>
<Search searchCocktails={this.handleSearchCocktails} clearCocktails={this.handleClearCocktails} showClear={this.state.cocktails.length>0?true:false} setAlert={this.handleSetAlert} />
<CocktailList loading={loading} cocktails={cocktails} />
</Fragment>
)} />
<Route exact path='/about' component={About} />
<Route exact path='/cocktailRecipe/:idDrink' render={props => (
<CocktailRecipe {...props} getCocktail={this.getCocktail} cocktailrecipe={cocktailrecipe} loading={loading}/>
)} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;````
In your screenshot, props cocktailrecipe is an array of object.
Use array desctructuring instead of object on CocktailRecipe.js
- const {strDrink} = this.props.cocktailrecipe;
+ const [strDrink] = this.props.cocktailrecipe;
So turns out that my wifi connection is part of the problem. And grabbing the wrong object.
In CocktailRecipe.js I added in:
line 22:
const { drinks } = this.props.cocktailrecipe;
and then put into the render():
{drinks && drinks[0].strDrink }
I'm told that this may not be the most elegant or efficient solution, so if anybody has a better way, please let me know.

Show search result component in another route

I am working with the MovieDB API. I want to show now playing movies on the root route but search result in another route.
I have tried putting history.push() method in handlesubmit but it shows error. Here's the code. Currently I am showing search result component in the home page itself.
App.js
import React, { Component } from "react";
import "./App.css";
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import Nav from "./component/Nav";
import axios from "axios";
import { Provider } from "./context";
import Home from "./component/Home";
import SearchResult from "./component/SearchResult";
import MovieDetails from "./component/movieDetails";
class App extends Component {
state = {
movieList: [],
searchResult: [],
currentpage: 1,
totalpage: 1,
API_KEY: "c51081c224217a3989b0bc0c4b3d3fff"
};
componentDidMount() {
this.getCurrentMovies();
}
getCurrentMovies = e => {
axios
.get(
`https://api.themoviedb.org/3/movie/now_playing?api_key=${
this.state.API_KEY
}&language=en-US&page=${this.state.currentpage}`
)
.then(res => {
this.setState({
movieList: res.data.results,
currentpage: res.data.page,
totalpage: res.data.total_pages
});
console.log(this.state);
});
};
getMovies = e => {
e.preventDefault();
const moviename = e.target.elements.moviename.value;
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${
this.state.API_KEY
}&query=${moviename}`
)
.then(res => {
this.setState({
searchResult: res.data.results
});
console.log(this.state.searchResult);
});
console.log(this.router);
};
nextPage = () => {
this.setState(
{
currentpage: (this.state.currentpage += 1)
},
() => console.log(this.state.currentpage)
);
this.getCurrentMovies();
};
prevPage = () => {
if (this.state.movieList && this.state.currentpage !== 1) {
this.setState(
{
currentpage: (this.state.currentpage -= 1)
},
() => console.log(this.state.currentpage)
);
this.getCurrentMovies();
}
};
render() {
const contextProps = {
myState: this.state,
getMovies: this.getMovies,
nextPage: this.nextPage,
prevPage: this.prevPage,
};
return (
<Provider value={contextProps}>
<BrowserRouter>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/:id" component={MovieDetails} />
</Switch>
</BrowserRouter>
</Provider>
);
}
}
export default App;
Home.js
import React, { Component } from "react";
import NowPlaying from "./NowPlaying";
import SearchResult from "./SearchResult";
import SearchBox from "./SearchBox";
class Home extends Component {
state = {};
render() {
return (
<div>
<SearchBox />
<SearchResult />
<NowPlaying />
</div>
);
}
}
export default Home;
SearchBox.js
import React, { Component } from "react";
import { MyContext } from "../context";
import { withRouter } from "react-router-dom";
class SearchBox extends Component {
static contextType = MyContext;
render() {
return (
<React.Fragment>
<div className="jumbotron jumbotron-fluid">
<div className="container" style={{ textAlign: "center" }}>
<h1 className="display-4">Find your Movie</h1>
<p className="lead">
Find rating, descrips and much more of your fev. movie.
</p>
<form onSubmit={this.context.getMovies}>
<input
name="moviename"
className="form-control mr-sm-2"
type="search, submit"
placeholder="Search"
aria-label="Search"
style={{ height: "50px" }}
/>
</form>
</div>
</div>
<div />
</React.Fragment>
);
}
}
export default withRouter(SearchBox);
SearchResult.js
import React, { Component } from "react";
import Movie from "./movie";
import { withRouter } from "react-router-dom";
import { MyContext } from "../context";
import SearchBox from "./SearchBox";
class SearchResult extends Component {
static contextType = MyContext;
render() {
return (
<React.Fragment>
<div className="container">
<div className="row justify-content-center">
{this.context.myState.searchResult.map(movie => {
return <Movie id={movie.id} image={movie.poster_path} />;
})}
</div>
{/* <button>Prev</button>
<button>Next</button> */}
</div>
</React.Fragment>
);
}
}
export default SearchResult;
and another thing. The pagination works for Now Playing Movies but couldn't make it to work with search result. Please help.
You can pass data with Redirect like this:
<Redirect to={{
pathname: '/movies',
state: { id: '123' }
}}
/>
and this is how you can access it:
this.props.location.state.id

React App Login Error : Maximum update depth exceeded

I am using react with redux to build login authentication system with backend passport jwt .
login works fine before , i have added PrivateRoute to some Routes that requires authentication.
Errors i got :
src/actions/authActions.js
import { GET_ERRORS,CLEAR_ERRORS,SET_CURRENT_USER,LOGOUT_USER} from './types';
import axios from 'axios';
import setAuthToken from '../utils/setAuthToken';
import jwt_decode from 'jwt-decode';
export const loginUser= userdata =>dispatch=>{
axios.post('/api/auth/login',userdata)
.then(res=>{
console.log('loginUser action response ==>',res.data);
const {token}=res.data;
localStorage.setItem('jwtToken',token);
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
dispatch(setCurrentUser(decoded));
}).catch(err=>{
dispatch({type:GET_ERRORS,payload:err.response.data});
})
}
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded
};
};
src/reducers/authReducers.js
import isEmpty from '../validation/is-empty';
import { SET_CURRENT_USER,LOGIN_USER,LOGOUT_USER} from '../actions/types';
const initialState = {
isAuthenticated: false,
user: {}
};
export default function(state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload
};
case LOGOUT_USER:
return {
...state,
isAuthenticated:false,
user: {}
};
default:
return state;
}
}
App.js
import React, { Component } from 'react';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {Provider} from 'react-redux';
import store from './store';
import Footer from './partials/footer';
import Header from './partials/header';
import Login from './components/auth/login';
import { setCurrentUser ,logoutUser} from './actions/authActions';
import jwt_decode from 'jwt-decode';
import setAuthToken from './utils/setAuthToken';
import PrivateRoute from './utils/PrivateRoute';
import Dashboard from './components/user/dashboard';
import NotFound404 from './components/error/404';
if(localStorage.jwtToken){
setAuthToken(localStorage.jwtToken);
// Decode token and get user info and exp
const decoded = jwt_decode(localStorage.jwtToken);
store.dispatch(setCurrentUser(decoded));
// Check for expired token
const currentTime = Date.now() / 1000;
if (decoded.exp < currentTime) {
// Logout user
store.dispatch(logoutUser());
// Clear current Profile
//store.dispatch(clearCurrentProfile());
// Redirect to login
window.location.href = '/login';
}
}
export default class App extends Component {
constructor(){
super();
this.state={
isAuthenticated:store.getState().auth.isAuthenticated
}
}
render() {
return (
<Provider store={store}>
<Router>
<div className="App">
<Header/>
<div className="container">
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/login" component={Login} />
<PrivateRoute isAuthenticated={this.state.isAuthenticated} exact path="/dashboard" component={Dashboard}/>
<Route component={NotFound404} />
</Switch>
</div>
<Footer/>
</div>
</Router>
</Provider>
);
}
}
src/components/login.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { loginUser } from '../../actions/authActions';
import { PropTypes } from 'prop-types';
class Login extends Component {
constructor(){
super();
this.state={
email:'',
password:'',
errors:{}
}
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({
[event.target.name]:event.target.value
});
}
handleSubmit(event){
event.preventDefault();
const user={
email:this.state.email,
password:this.state.password
}
this.props.loginUser(user);
}
componentDidMount() {
if (this.props.auth.isAuthenticated) {
this.props.history.push('/dashboard');
}
}
componentWillReceiveProps(nextProps){
if(nextProps.errors){
this.setState({
errors:nextProps.errors
});
}
if(nextProps.auth.isAuthenticated){
this.props.history.push('/dashboard');
}
}
render () {
const {errors} = this.state;
return (
<div className="row my-5">
<div className="col-md-4 offset-md-4 col-sm-12">
<div className="card shadow-sm">
<h5 className="card-header">Login</h5>
<div className="card-body">
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="email" className="label">Email</label>
<input type="email" id="email" name="email" value={this.state.email} onChange={this.handleChange} className={classnames('form-control',{'is-invalid':errors.email})}/>
{errors.email && (<div className="invalid-feedback">{errors.email}</div>)}
</div>
<div className="form-group">
<label htmlFor="password" className="label">Password</label>
<input type="password" id="password" name="password" value={this.state.password} onChange={this.handleChange} className={classnames('form-control',{'is-invalid':errors.password})}/>
{errors.password && (<div className="invalid-feedback">{errors.password}</div>)}
</div>
<button type="submit" className="btn btn-success btn-block">Login</button>
</form>
<div className="py-3 border-bottom"></div>
<Link to="/register" className="btn btn-default btn-block my-2">Haven't created account yet ?</Link>
<Link to="/forgotpassword" className="btn btn-default btn-block">Forgot Password ?</Link>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => ({
auth:state.auth,
errors:state.errors
})
const mapDispatchToProps = {
loginUser
}
Login.propTypes={
auth:PropTypes.object.isRequired,
errors:PropTypes.object.isRequired,
loginUser:PropTypes.func.isRequired
}
export default connect(mapStateToProps,mapDispatchToProps)(Login)
PrivateRoute.js component
import React from 'react';
import {Route,Redirect} from 'react-router-dom';
const PrivateRoute=({component: Component, isAuthenticated, ...rest}) => {
return (
<Route
{...rest}
render={(props) => isAuthenticated === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
export default PrivateRoute;
Please help me to solve this error .
I have solved my error by replacing PrivateRoute component as below :
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
const PrivateRoute = ({ component: Component,auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
I suggest you use another state variable for saving request status. like loggingIn if it's true, show loading if it's false and isAuthenticated is false, the user is not requested for login and it's not logged in. so redirect it to /login.
PrivateRoute.js component
import React from 'react';
import {Route,Redirect} from 'react-router-dom';
class PrivateRoute extends Component {
render() {
const {
component: Component, loggingIn, isAuthenticated, ...rest
} = this.props;
if (loggingIn) {
return (
<div>
Please wait.
</div>
);
}
return (<Route {...rest} render={props => (isAuthenticated ? (<Component {...props} />) : (<Redirect to={{ pathname: '/login', state: { from: props.location } }} />))} />);
}}
export default PrivateRoute;
src/actions/authActions.js
import { GET_ERRORS,CLEAR_ERRORS,SET_CURRENT_USER,LOGOUT_USER} from './types';
import axios from 'axios';
import setAuthToken from '../utils/setAuthToken';
import jwt_decode from 'jwt-decode';
export const loginUser= userdata =>dispatch=>{
dispatch(loggingIn(true));
axios.post('/api/auth/login',userdata)
.then(res=>{
dispatch(loggingIn(false));
console.log('loginUser action response ==>',res.data);
const {token}=res.data;
localStorage.setItem('jwtToken',token);
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
dispatch(setCurrentUser(decoded));
}).catch(err=>{
dispatch(loggingIn(false));
dispatch({type:GET_ERRORS,payload:err.response.data});
})
}
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded
};
};
export const loggingIn = status => {
return {
type: 'LOGGINGIN',
status,
}
}
src/reducers/authReducers.js
import isEmpty from '../validation/is-empty';
import { SET_CURRENT_USER,LOGIN_USER,LOGOUT_USER} from '../actions/types';
const initialState = {
isAuthenticated: false,
user: {}
};
export default function(state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload
};
case LOGOUT_USER:
return {
...state,
isAuthenticated:false,
user: {}
};
case 'LOGGINGIN':
return {
...state,
loggingIn: action.status,
};
default:
return state;
}
}
and remember pass loggingIn as props to privateRoute
Edit : show how to use in App.js
App.js
import React, { Component } from 'react';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {connect} from 'react-redux';
import Footer from './partials/footer';
import Header from './partials/header';
import Login from './components/auth/login';
import { setCurrentUser ,logoutUser} from './actions/authActions';
import jwt_decode from 'jwt-decode';
import setAuthToken from './utils/setAuthToken';
import PrivateRoute from './utils/PrivateRoute';
import Dashboard from './components/user/dashboard';
import NotFound404 from './components/error/404';
class App extends Component {
constructor(props){
super(props);
const { dispatch } = props;
if(localStorage.jwtToken){
setAuthToken(localStorage.jwtToken);
// Decode token and get user info and exp
const decoded = jwt_decode(localStorage.jwtToken);
dispatch(setCurrentUser(decoded));
// Check for expired token
const currentTime = Date.now() / 1000;
if (decoded.exp < currentTime) {
// Logout user
dispatch(logoutUser());
// Clear current Profile
//dispatch(clearCurrentProfile());
// Redirect to login
window.location.href = '/login';
}
}
}
render() {
const { isAuthenticated, loggingIn } = this.props;
return (
<Provider store={store}>
<Router>
<div className="App">
<Header/>
<div className="container">
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/login" component={Login} />
<PrivateRoute loggingIn={loggingIn} isAuthenticated={isAuthenticated} exact path="/dashboard" component={Dashboard}/>
<Route component={NotFound404} />
</Switch>
</div>
<Footer/>
</div>
</Router>
</Provider>
);
}
}
const mapStateToProps = state = {
const { loggingIn, isAuthenticated } = state.auth;
return { loggingIn, isAuthenticated }
}
export default connect(mapStateToProps)(App);

Resources