I am trying to create a PrivateRoute(HOC) to test if a user has been authenticated(check is 'auth' exist in redux store) before sending them to the actual route. The issue is the privateroute finishes before my auth shows up in redux store.
The console.log runs twice, the first time, auth doesnt appear in the store, but it does the second time, but by that time, its already routed the user to the login screen.... How can I give enough time for the fetch to finish? I know how to do this condition when I simply want to display something conditionally(like login/logout buttons) but this same approach does not work when trying to conditionally route someone.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route } from 'react-router-dom'
class PrivateRoute extends Component {
render() {
const { component: Component, ...rest } = this.props
console.log(this.props)
return (
<Route {...rest} render={(props) => (props.auth ? <Component {...props} /> : props.history.push('/login'))} />
)
}
}
function mapStateToProps({ auth }) {
return { auth }
}
export default connect(mapStateToProps)(PrivateRoute)
I didn't use redux here, but I think you would get the main point. Hope this will help and feel free to ask any questions!
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import Dashboard from "path/to/pages/Dashboard";
class App extends Component {
state = {
isLoggedIn: null,
};
componentDidMount () {
// to survive F5
// when page is refreshed all your in-memory stuff
// is gone
this.setState({ isLoggedIn: !!localStorage.getItem("sessionID") });
}
render () {
return (
<BrowserRouter>
<Switch>
<PrivateRoute
path="/dashboard"
component={Dashboard}
isLoggedIn={this.state.isLoggedIn}
/>
<Route path="/login" component={Login} />
{/* if no url was matched -> goto login page */}
<Redirect to="/login" />
</Switch>
</BrowserRouter>
);
}
}
class PrivateRoute extends Component {
render () {
const { component: Component, isLoggedIn, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
}
class Login extends Component {
state = {
login: "",
password: "",
sessionID: null,
};
componentDidMount () {
localStorage.removeItem("sessionID");
}
handleFormSubmit = () => {
fetch({
url: "/my-app/auth",
method: "post",
body: JSON.strigify(this.state),
})
.then(response => response.json())
.then(data => {
localStorage.setItem("sessionID", data.ID);
this.setState({ sessionID: data.ID });
})
.catch(e => {
// error handling stuff
});
};
render () {
const { sessionID } = this.state;
if (sessionID) {
return <Redirect to="/" />;
}
return <div>{/* login form with it's logic */}</div>;
}
}
When your action creator return the token, you need to store it in localStorage. and then you can createstore like below,
const store = createStore(
reducers,
{ auth: { authenticated : localStorage.getItem('token') }},
applyMiddleware(reduxThunk)
)
if user already logged in then token will be there. and initial state will set the token in store so you no need to call any action creator.
Now you need to secure your components by checking if user is logged in or not. Here's the HOC for do that,
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};
Related
I'm learning to use React JS.
I have the following page.
Home
Login
Note
Create Note
My case is as follows.
Home can be accessed without logging in
Note and create notes cannot be accessed without logging in
How to make the case above work?
Here's the code snippet I made:
index.js
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<BrowserRouter> // from "react-router-dom"
<App />
</BrowserRouter>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js as entry home page
import React, { Component } from "react";
import AuthService from "./services/auth.service";
import Routes from "./config/routes";
// Lot of import bootstrap dan font-awesome and css
class App extends Component {
constructor(props) {
super(props);
this.logOut = this.logOut.bind(this);
this.state = {
currentUser: undefined,
backendSupportInformation: undefined,
};
}
componentDidMount() {
const user = AuthService.getCurrentUser();
if (user) {
this.setState({
currentUser: user,
backendSupportInformation: user.backend,
});
}
}
logOut() {
AuthService.logout();
}
render() {
const { currentUser, backendSupportInformation } = this.state;
return (
<div>
<header>
<nav className="navbar navbar-expand-sm navbar-dark bg-dark">
// some of link here
</nav>
</header>
<main role="main" className="container-fluid mt-3">
<Routes /> // use react-route-dom
</main>
</div>
);
}
}
export default App;
Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
const Routes = () => {
return (
<Switch>
<Route exact path={["/", "/home"]} component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/note" component={Note} />
<Route exact path="/note/create" component={NoteCreate} />
<Route exact path="/profile" component={Profile} />
</Switch>
);
};
export default Routes;
Now i am doing in NoteComponent like this.
NoteComponent
export default class Note extends Component {
state = {
redirect: null,
userReady: false,
};
componentDidMount() {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) this.setState({ redirect: "/home" });
this.setState({ currentUser: currentUser, userReady: true });
this.retrieveAll();
}
render() {
if (this.state.redirect) {
// pass message that you need login first to access this note page
return <Redirect to={this.state.redirect} />;
}
}
I dont want to repeat my self into NoteCreate Component?
Any advice it so appreciated.
Just as a note to start, not sure which resources you're using to learn React, but as of now I would highly recommend you look into a modern course which teaches React with Hooks, aside from to get error boundaries (which with react-error-boundary) there is no reason to be writing class components.
Regarding the issue at hand, you didn't specifically mention any errors so this seems to be a question of "how should I go about this" as opposed to actually fixing something? Let me know if theres specific errors and I'll try to adjust my answer to help further.
I would recommend refactoring the logic you have in your Note component into a component of itself, so that you can wrap your routes with it. Store the information for whether they're authenticated into a context, and then wrap your routes with that context provider so you can consume that context in your child components, without duplicating that logic on each page.
You need to create a RouterWithAuth Component and use that instead of using Router directly, something like this:
export default class RouteWithAuth extends Component {
state = {
redirect: null,
userReady: false,
};
componentDidMount() {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) this.setState({ redirect: "/home" });
this.setState({ currentUser: currentUser, userReady: true });
this.retrieveAll();
}
render() {
const { redirect, userReady } = this.state;
if (redirect) {
// pass message that you need login first to access this note page
return <Redirect to={this.state.redirect} />;
} else if (userReady) {
return (
<Route
exact={props.exact}
path={props.path}
component={props.component}
/>
);
} else {
return <div>Loading....</div>;
}
}
}
which a cleaner way of creating RouteWithAuth might be to use React Function Component like this:
export default function RouteWithAuth() {
const [redirect, setRedirect] = useState(null);
const [userReady, setUserReady] = useState(false);
useEffect(() => {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) {
setRedirect("/home");
return;
}
//Do Something with the currentUser such as storing it in redux store or in context for later use cases
setUserReady(true);
}, []);
if (redirect) {
return <Redirect to={redirect} />;
} else if (userReady) {
return (
<Route
exact={props.exact}
path={props.path}
component={props.component}
/>
);
} else {
return <div>Loading....</div>;
}
}
Bhai log ye adnan ka pucha wa question hai jab m usko react sikha ra tha usko questions etc krne k liye m apna account diya tha cz pehly account verify krwany k liye baqaida coding challange pass krna hota tha (khud ko legit programmer show krwane k liye), Lekin ab kiu k log meri profile dekhty hain tu mujhe embarrassment hoti hai ;(
I'm using React with Node & express and authenticating with passport and passport local, I want to know that how can I protect a route in React, I mean I want to show a component only if user is authenticated and otherwise I want it to redirect on login page.
In my case I want to protect Notes route,
The method I'm trying is not working at all...
My React Code
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import axios from 'axios';
import Register from './Components/Register';
import Login from './Components/Login';
import Notes from './Components/Notes';
function App () {
//Function to check authentication from server
const checkAuth = () => {
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then(res => res.data)
}
return (
<Switch>
<Route exact path='/' component={Register} />
<Route exact path='/login' component={Login} />
<Route exact path='/notes' render={() => {
return checkAuth()? (
<Notes />
) : (
<Redirect to="/login" />
)
}} />
</Switch>
);
};
export default App;
And My server side code
//Notes Page
router.get('/notes', (req, res) => {
if(req.isAuthenticated()) {
res.send(req.user.email);
}else{
res.send(false);
}
};
Consider showing a loader until the api call returns a value. Once the api call has returned a value then render the required component using a higher order component.
App.jsx
class App extends Component {
state = {
login: false,
loading: false,
}
componentDidMount() {
//Function to check authentication from server
this.setState( {loadnig:true}, () => {
this.checkAuth()
}
}
checkAuth = () => {
// API request to check if user is Authenticated
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then( res => {
console.log(res.data);
// API response with data => login success!
this.setState({
login: true,
loading:false
});
});
.catch( error => {
console.error(error);
// Handle error
this.setState({
loading:false
});
});
}
render() {
let {login, loading} = this.state
let protectedRoutes = null;
if(loading){
return <loader/>
}
return (
<Switch>
//use authorize HOC with three parameters: component, requiresLogin, isLoggedIn
<Route exact path='/path' component={authorize(component, true, login)} />
<Route exact path='/' component={Register} />
<Route exact path='/login' component={Login} />
</Switch>
);
}
};
export default App;
Using a Higher Order Component will give you the flexibility to control routes based on the login status of the users.
Authorize,jsx
export default function (componentToRender, requiresLogin, isLoggedIn) {
class Authenticate extends React.Component<> {
render() {
if (requiresLogin) {
//must be logged in to view the page
if (!isLoggedIn) {
// redirect to an unauthorised page or homepage
this.props.history.push('/unauthorised');
return;
// or just return <unauthoriesd {...this.props} />;
}
} else {
// to be shown to non loggedin users only. Like the login or signup page
if (isLoggedIn) {
this.props.history.push('/unauthorised');
return;
//or just return <home {...this.props} />;
}
}
//if all the conditions are satisfied then render the intended component.
return <componentToRender {...this.props} />;
}
}
return Authenticate;
}
Also, if you ever decide to add in more conditions for the routing then you can easily add those to the HOC.
checking login status via "server request" takes time! Consider then to create a Class with state!, i'll give you an example below:
import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import axios from 'axios';
import Register from './Components/Register';
import Login from './Components/Login';
import Notes from './Components/Notes';
class App extends Component {
state = {
login: false
}
componentDidMount() {
//Function to check authentication from server
this.checkAuth()
}
checkAuth = () => {
// API request to check if user is Authenticated
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then( res => {
console.log(res.data);
// API response with data => login success!
this.setState({
login: true
});
});
}
render() {
let protectedRoutes = null;
if(this.state.login === true) {
// if login state is true then make available this Route!
protectedRoutes = <Route exact path='/notes' component={Notes} />
}
return (
<Switch>
<Route exact path='/' component={Register} />
<Route exact path='/login' component={Login} />
{ protectedRoutes }
</Switch>
);
}
};
export default App;
I hope this example will be useful to you.
Your function checkAuth since it makes a network call may not return the correct value in time for rendering, what you SHOULD do is create a state for whether or not the user is authenticated and have checkAuth update that state.
const [authenticated, updateAuthenticated] = useState(false);
...
update your checkAuth to update the authenticated state
//Function to check authentication from server
const checkAuth = () => {
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then(res => updateAuthenticated(res.data)); // double check to ensure res.data contains the actual value returned
}
...
update your rendering to use the state
<Route exact path='/notes' render={() => {
return (authenticated ?
(<Notes />)
:
(<Redirect to="/login" />)
)
}} />
... with this approach you can also set the default in useState to true or false and determine whether or not it's your server side code that's giving trouble
... dont for get to call checkAuth in a useEffect somewhere at the top
useEffect(()=>{
checkAuth()
},[])
Replace your code below
<Route exact path='/notes' render={() => {
checkAuth()? (
<Notes />
) : (
<Redirect to="/login" />
)
}} />
With this code please
<Route exact path="/notes">{checkAuth ? <Notes /> : <Redirect to="/login" />}</Route>
I have:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from "prop-types";
import {Router, Route, Switch} from 'react-router-dom'
import { Redirect } from "react-router";
import history from './History';
import Home from '../containers/Home';
import Login from '../containers/LogIn';
import CreateUsers from '../containers/CreateUsers';
import Dashboard from '../containers/Dashboard';
import NavBar from './NavBar';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false
};
fetch("/api/user")
.then(function (response) {
return response.json();
})
.then(function (res) {
console.log(res);
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
// Redirect the user only if they are on the login page.
history.push("/dashboard");
} else {
history.push("/login");
}
});
}
componentDidMount() {
}
render() {
return (
<Router history={history}>
<div>
<NavBar />
<Route>
<Redirect from="/" to="/login" /> // If not authenticated, how???
</Route>
<Route path="/login" component={Login}/>
<Route path="/dashboard" component={Dashboard}/>
</div>
</Router>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
I have two issues, the first is how can I get it to redirect back to the login using Router if the user failed authentication, currently I'm getting the error: React.Children.only expected to receive a single React element child
The other issue is that it can't see this from:
.then(function (res) {
console.log(res);
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
...
Giving me Uncaught (in promise) TypeError: Cannot read property 'setState' of undefined
1) use isAuthenticated state to redirect
!this.state.isAuthenticated && (<Route>
<Redirect from="/" to="/login" /> // If not authenticated, how???
</Route>)
2) use arrow function instead then you will have this from current context binded to the callback
.then((res) => {
console.log(res);
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
// Redirect the user only if they are on the login page.
history.push("/dashboard");
} else {
history.push("/login");
}
});
1st issue:
Use state to trigger your <Redirect/>. When your fetch finishes we can use the response to update the isAuthenticated state so that it triggers a re-render.
.then((res) => {
this.setState({
isAuthenticated: res.enabled === 1,
});
});
render() {
return (
<Router history={history}>
<div>
{this.state.isAuthenticated && <Redirect from="/" to="/login" />}
</div>
</Router>
)
}
2nd issue:
This doesn't work because you're creating a new function for the response, you can solve this by changing it into an arrow function so that this points to the class OR bind then to this.
.then((res) => {
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
}
});
.then(function(res) => {
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
}
}).bind(this);
Trying to solve your question step by step
1st how can I get it to redirect back to the login using Router if the user failed authentication
I won't advise for your current approach because everytime you do an authentication (let's say from Facebook, Google), The callback will again reach your App Component, Again make a request and this would cause the app to go in endless loop.
Also, Too avoid side effects all the requests should be made in componentDidMount
Personal Suggestion: Use Redux here.
This is how I did client side Authentication using Redux
const AuthenticatedRoutes = ({component: Component, ...props})=> {
return (
<Route {...props} render= {(prop) => {
return (
props.prop.isAuthenticated ? <Component {...prop} /> :
<Redirect to={{
pathname: "/login",
}}/>
)}
}/>
)}
//These roots can;t be seen by users who are authenticated
const NotAuthenticatedRoutes = ({component: Component, ...props}) => (
<Route {...props} render= {(prop) => {
return (
!props.prop.isAuthenticated ? <Component {...prop} /> :
<Redirect to={{
pathname: "/",
}}/>
)}
}/>
)
class route extends Component {
render () {
return(
<BrowserRouter>
<div>
<Switch>
<NotAuthenticatedRoutes exact path ="/login" component={Login} prop={this.props} />
<AuthenticatedRoutes exact path ="/" component={HomeScreen} prop={this.props} />
</Switch>
</div>
</BrowserRouter>
)
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.profileInfo.isAuthenticated,
isLoaded: state.profileInfo.isLoaded,
googleProfileLoading: state.profileInfo.googleProfileLoading
}
};
export default connect(mapStateToProps,
{
googleProfile
})(route)
Here, If the state of the user in my app isn't set to authenticated, Inside my login Component, I make an API call for login and then set Redux State
2nd: Copying this from Win answer, Check your function for lexical scoping
.then((res) => {
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
}
});
.then(function(res) => {
if (res.enabled === 1) {
this.setState({
isAuthenticated: true
});
}
}).bind(this);
Also, Use ComponentDidMount for Making api calls to avoid any side effects
Your initial code will work same as it is with minor change. Before making promise, take this in a variable like below-
let me = this;
and when you promise is completed and you are updating state, use like below -
me.setState({
isAuthenticated: true
});
It will work fine.
I am trying to navigate to another page after a successful axios call which dispatch the action to change the state. But, I am sure that i wrongly call dispatch inside the axios call which result below error. I have tried many other ways like using ES6 arrow function to call the dispatch method but never works.
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
App.js
import React, { Component } from 'react';
import axios from 'axios';
import history from './History';
import { Redirect } from "react-router-dom";
import './App.css';
import { connect } from 'react-redux';
import * as actionType from './reducer/action';
class App extends Component {
constructor(){
super();
this.state = {
username: '',
password: '',
loginData: []
};
this.handleUserChange = this.handleUserChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event){
event.preventDefault();
axios.post('https://api.github.com/user',{}, {
auth: {
username: this.state.username,
password: this.state.password
}
}).then((response) => {
console.log(response.data);
this.props.onLoginAuth(true);// This is where i am getting ERROR
//history.push({pathname: '/home', state: { detail: response.data }});
//history.go('/home');
this.setState({
loginData : response.data,
});
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
handleUserChange(event){
this.setState({
username : event.target.value,
});
}
handlePasswordChange = event => {
this.setState({
password: event.target.value
});
}
render() {
if(this.props.authenticated){
console.log("Redirecting to Home page " + this.props.authenticated);
return <Redirect to={{ pathname: '/home', state: { detail: this.state.loginData } }}/>
}
return (
<div className='loginForm'>
<form onSubmit={this.handleSubmit}>
<label>
username :
<input type="text" value={this.state.username} onChange={this.handleUserChange} required/>
</label>
<label>
password :
<input type="password" value={this.state.password} onChange={this.handlePasswordChange} required/>
</label>
<input type="submit" value="LogIn" />
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
authenticated: state.authenticated
};
}
const mapDispatchToProps = dispatch => {
return {
onLoginAuth: (authenticated) => dispatch({type: actionType.AUTH_LOGIN, authenticated:authenticated})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import PrivateRoute from './PrivateRoute';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import {
Router,
Redirect,
Route,
Switch
} from "react-router-dom";
import Home from './Home';
import User from './User';
import history from './History';
import reducer from './reducer/Login';
const store = createStore(reducer);
ReactDOM.render(
<Provider store= {store} >
<Router history={history}>
<Switch>
<Route path="/" exact component={App} />
<PrivateRoute path="/home" component={Home} />
<PrivateRoute path="/user" component={User} />
</Switch>
</Router>
</Provider>,
document.getElementById('root'));
registerServiceWorker();
Home.js
import React, { Component } from 'react';
import axios from 'axios';
import Autosuggest from 'react-autosuggest';
import './Home.css';
import history from './History';
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
const getSuggestionValue = suggestion => suggestion;
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion}
</div>
);
class Home extends Component {
constructor(props) {
super(props);
// Autosuggest is a controlled component.
// This means that you need to provide an input value
// and an onChange handler that updates this value (see below).
// Suggestions also need to be provided to the Autosuggest,
// and they are initially empty because the Autosuggest is closed.
this.state = {
value: '',
suggestions: [],
timeout: 0
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
console.log('=====++++ ' + newValue);
};
onSuggestionSelected = (event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) => {
console.log("Get the user +++++++++ " + suggestionValue);
if(suggestionValue && suggestionValue.length >= 1){
axios.get('https://api.github.com/users/'+ suggestionValue)
.then((response) => {
console.log("user selected : "+ response.data.avatar_url);
history.push({pathname: '/user', state: { detail: response.data }});
history.go('/user');
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.getSuggestions();
}, 500);
};
getSuggestions = () =>{
if(this.state.value && this.state.value.length >= 1){
axios.get('https://api.github.com/search/users',{
params: {
q: this.state.value,
in:'login',
type:'Users'
}
}).then((response) => {
console.log("users login : "+ response.data.items);
const userNames = response.data.items.map(item => item.login);
console.log("===== " + userNames);
this.setState({
suggestions: userNames
})
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Type a userName',
value,
onChange: this.onChange
};
return (
<div>
<div>
Home page {this.props.location.state.detail.login}
</div>
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={this.onSuggestionSelected}
/>
</div>
</div>
);
}
}
export default Home;
PrivateRouter.js
import React from 'react';
import {
Redirect,
Route,
} from "react-router-dom";
const PrivateRoute = ({ component: Component, ...rest}) => (
<Route
{...rest}
render={props =>
props.authenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute;
Can you help me how i can call onLoginAuth from axios?
I don't think that the problem comes from the axios call. Your axios call the dispatch which update the state authenticated. So everytime the state authenticated is updated, the render method of App component is called.
The condition if(this.props.authenticated) is verified, and Redirect to /home is called. But somehow, your router routes again to /. The App component is rerender. The condition if(this.props.authenticated) is true again, and the routes routes again to App. It creates an infinite loop, that's why you see the message Maximum update depth exceeded.
In the index.js, for testing purpose, replace all PrivateRoute by Route to see if the route works correctly. If it works, the problem may come from your PrivateRoute component.
I've got react-toastify element in my App.js component implemented this way:
class App extends Component {
componentDidUpdate(prevProps) {
const { toast } = this.props
if(toast.id !== prevProps.toast.id) {
this.notify(toast)
}
}
notify = (data) => {
switch(data.type) {
case TOAST.TYPE.ERROR:
...
return toast.show()
}
}
render() {
return (
<BrowserRouter>
<div className="app">
<Switch>
<Route path={ getRoutePath('password.set') } component={ PasswordSet } />
<Route path={ getRoutePath('password.reset') } component={ PasswordReset } />
<Route path={ getRoutePath('login') } component={ LoginSection } />
<Route path={ getRoutePath('home') } component={ AuthenticatedSection } />
</Switch>
<ToastContainer
className="custom-toastify"
autoClose={ 5000 }
hideProgressBar={ true }
closeButton={ <CloseButton /> }
/>
</div>
</BrowserRouter>
)
}
}
function mapStateToProps({ toast }) {
return { toast }
}
Now consider the following scenario: I've got a UsersAdmin PureComponent inside AuthenticatedSection where you can enable/disable users. When you click on enable/disable button, the UsersAdmin component re-renders because of users redux state change and then it also re-renders, because I'm showing toast on success/error api call.
toggleUsersDisabled = (user) => () => {
const { modifyUser, showToast } = this.props
modifyUser(user.id, {
disabled: user.disabled === 0 ? 1 : 0
}).then((response) => {
showToast(`${response.value.name} has been ${response.value.disabled ? 'disabled' : 'enabled'}`)
}).catch(_noop)
}
The showToast dispatches new message to redux state for toasts. Is it possible to somehow prevent re-rending of child components when the toast is shown?
Edit:
added UsersAdmin redux connection including selector
// users selector
import { createSelector } from 'reselect'
const getUsers = state => state.users.get('data')
const getIsFulfilled = state => state.users.get('isFulfilled')
export const getFulfilledUsers = createSelector(
[getUsers, getIsFulfilled],
users => users
)
// UsersAdmin
const mapStateToProps = (state) => {
return {
users: getFulfilledUsers(state)
}
}
UsersAdmin.propTypes = {
users: PropTypes.object.isRequired,
fetchUsersList: PropTypes.func.isRequired,
modifyUser: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired,
showToast: PropTypes.func.isRequired
}
export default connect(mapStateToProps, { fetchUsersList, modifyUser, deleteUser, showToast })(UsersAdmin)
I don't really get why you added all the toasts login inside your App.js
if you look over the docs in:
https://github.com/fkhadra/react-toastify#installation
the only thing you need to do is adding <ToastContainer /> to your app and you are done, exactly like you have in your example.
Now for calling toasts you just import:
import { toast } from 'react-toastify';
in to any component you like in the system, and now you just run the toast function and you got yourself a toast.
ie:
import { Component } from 'react';
import { toast } from 'react-toastify';
class SomeComponent extends Component {
showToast() {
toast('you now see a toast');
}
render() {
return <button onClick={()=>this.showToast()}>toast it</button>;
}
}