I'm building an admin app for a project with react, redux, react-router and react-router-redux. React-router is v4.0.0, react-router-redux is v5.0.0-alpha.3 (installed with npm install react-router-redux#next). What I'm trying is:
Load app,
Perform an async call to backend to see if the user is logged in (token stored in a cookie),
If user is not logged in, redirect to /login and render Login component.
For async actions I'm using redux-thunk.
Root.js
import React, { Component, PropTypes } from 'react';
import { Provider, connect } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import { ConnectedRouter, push } from 'react-router-redux';
import Login from './Login';
const App = () => <h1>Dashboard</h1>;
const NotFound = () => <h1>Not found :(</h1>;
class Root extends Component {
// use componentDidMount as recommended here:
// https://facebook.github.io/react/docs/react-component.html#componentdidmount
componentDidMount() {
const { dispatch, user } = this.props;
if (!user) {
dispatch(push('/login'));
}
}
render() {
const { store, history } = this.props;
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Switch>
<Route exact path='/' component={App} />
<Route exact path='/login' component={Login} />
<Route component={NotFound} />
</Switch>
</div>
</ConnectedRouter>
</Provider>
);
}
}
Root.propTypes = {
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
user: PropTypes.shape({
email: PropTypes.string.isRequired
})
};
const mapStateToProps = state => ({
ready: state.ready,
user: state.user
});
export default connect(mapStateToProps)(Root);
Login.js
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import {
loginFormChange,
loginFormSubmit
} from '../actions';
class Login extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const { target } = event,
{ value, name } = target,
{ dispatch } = this.props;
dispatch(loginFormChange({
[name]: value
}));
}
handleSubmit(event) {
event.preventDefault();
const { dispatch, login } = this.props,
{ email, password } = login;
dispatch(loginFormSubmit({
email,
password
}));
}
render() {
const { login } = this.props,
{ email, password } = login;
return (
<form onSubmit={this.handleSubmit}>
<input type="email" name="email" value={email} onChange={this.handleChange} />
<input type="password" name="password" value={password} onChange={this.handleChange} />
<button type="submit">Sign in</button>
</form>
);
}
}
Login.propTypes = {
dispatch: PropTypes.func.isRequired,
login: PropTypes.shape({
email: PropTypes.string.isRequired,
password: PropTypes.string.isRequired
}).isRequired
};
const mapStateToProps = state => ({
login: state.login
});
export default connect(mapStateToProps)(Login);
actions.js
export const LOGIN_FORM_CHANGE = 'Login form change';
export const LOGIN_FORM_SUBMIT = 'Login form submit';
export const AUTHENTICATE_USER = 'Authenticate user';
export const loginFormChange = data => {
const { email, password } = data;
return {
type: LOGIN_FORM_CHANGE,
email,
password
};
};
export const loginFormSubmit = data => dispatch => {
const { email, password } = data;
return fetch('/api/auth/token', {
headers: {
'Authorization': 'Basic ' + btoa([ email, password ].join(':'))
},
credentials: 'same-origin'
})
.then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then(user => {
// this line will throw setState warning:
// Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
dispatch(authenticateUser(user));
});
};
export const authenticateUser = data => {
const { email } = data;
return {
type: AUTHENTICATE_USER,
email
};
};
I want to point out that I'm using the recommended approach to async actions, described in redux documentation. I won't post my reducers for brevity. Finally:
index.js
import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import reducers from './reducers';
import Root from './containers/Root';
const history = createHistory(),
middleware = [
routerMiddleware(history),
thunk
];
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger());
}
const store = createStore(
reducers,
applyMiddleware(...middleware)
);
render(
<Root store={store} history={history} />,
document.getElementsById('root')
);
So the warning gets thrown in the loginFormSubmit async action, when it tries to dispatch a sync authenticateUser action. Moreover it happens only after a redirect. I've tried different redirect approaches:
push from react-router-redux
Redirect component from react-router
I've also tried putting the redirect call in different places (componentWillMount, componentDidMount, componentWillReceiveProps, conditional rendering inside of the component, using conditional PrivateRoute components as described in the react-router documentation, etc.), but nothing seems to work.
If there is no redirect in the first place (e.g. a user opens /login page instead of a protected one), than there is no warning.
Any help on the issue is very much appreciated.
I am having the same issue and basically it's a bug with the ConnectedRouter from react-router-redux v5.0.0-alpha.2 and alpha.3
It was actively being discussed for the past few days but now it's fixed in alpha 4 and the issue is closed:
https://github.com/ReactTraining/react-router/issues/4713
Related
For a small project of mine, I'm trying to implement the most basic authentication as possible, using the React context API without Redux.
import { createContext, useContext, useState } from 'react'
export const AuthContext = createContext()
export const useAuth = () => {
const context = useContext(AuthContext)
if(context === null) throw new Error('Nope')
return context
}
export const AuthProvider = (props) => {
const [authenticated, setAuthenticated] = useState(false)
const login = () => {
setAuthenticated(true)
localStorage.setItem(storageKey, true)
}
const logout = () => {
setAuthenticated(false)
localStorage.setItem(storageKey, false)
}
return <AuthContext.Provider value={{authenticated, login, logout}} {...props}/>
}
export default AuthContext
I created a context, and wrapped my <App /> component in it like so; <AuthProvider></App></AuthProvider>. Because I want to keep the authenticated state, I used the browser's local storage, for storing a simple boolean value.
import PrivateRoute from './PrivateRoute'
import { useAuth } from './context/AuthContext'
import { AuthPage } from './pages'
import {
BrowserRouter,
Switch,
Route,
} from 'react-router-dom'
import { useEffect } from 'react'
const App = () => {
const { login, authenticated } = useAuth()
useEffect(() => {
const token = localStorage.getItem('user')
if(token && token !== false) { login() }
})
return (
<BrowserRouter>
<Switch>
<PrivateRoute exact path="/auth" component={AuthPage} />
<Route exact path='/'>
Dashboard
</Route>
</Switch>
</BrowserRouter>
)
}
export default App
Then, in my <App /> component, I tried invoking the login callback, given from the AuthProvider, which made me assume that made me login during page refreshes. When I try to access the authenticated variable in the current component, it does work. It shows that I am authenticated.
However when I try to set up a PrivateRoute, which only authenticated users can go to like this:
import {
Route,
Redirect
} from 'react-router-dom'
import { useAuth } from './context/AuthContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const { authenticated } = useAuth()
if(authenticated) {
return <Route {...rest} render={(props) => <Component {...props} />} />
}
return <Redirect to={{ pathname: '/login' }} />
}
export default PrivateRoute
It does not work. It just redirects me to the login page. How does this come? The PrivateRoute component is getting rendered from the <App /> component. Also, what would be the solution to this problem?
Rather than running a useEffect on every rerender to check if user should be logged in, you should better initialize your authenticated state with the values from your localStorage:
const storageKey = 'user'
const initialState = JSON.parse(localStorage.getItem(storageKey)) ?? false
export const AuthProvider = (props) => {
const [authenticated, setAuthenticated] = useState(initialState)
const login = () => {
setAuthenticated(true)
localStorage.setItem(storageKey, true)
}
const logout = () => {
setAuthenticated(false)
localStorage.setItem(storageKey, false)
}
return <AuthContext.Provider value={{authenticated, login, logout}} {...props}/>
}
Thanks to Yousaf for the explaination in the comments and the HospitalRun project on GitHub, I made a loading state in the <App /> component.
import { useAuth } from './context/AuthContext'
import { useEffect, useState } from 'react'
import Router from './Router'
const App = () => {
const [ loading, setLoading ] = useState(true)
const { login } = useAuth()
const token = localStorage.getItem('user')
useEffect(() => {
if(token && token !== false) {
login()
}
setLoading(false)
}, [loading, token, login])
if (loading) return null
return <Router />
}
export default App
Here I only let anything render, after the login function was called.
if (loading) return null
If this could be done any better, feedback would still be appriciated!
I am unable to set context state in my app.js, I get empty values in it somehow, but can access it in child component.
I want to set context state in app.js whenever user comes to page so that I can use it throughout the application, for example show different headers based on whether user is logged in or not
SandBox URL as requested -> https://codesandbox.io/s/quizzical-snyder-2qghj?file=/src/App.js
I am following https://upmostly.com/tutorials/how-to-use-the-usecontext-hook-in-react
app.js
// import installed dependencies
import React, { useEffect, useContext } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
// import custom contexts
import { AuthContext, AuthContextProvider } from './contexts/auth/AuthContext';
// import pages
import Homepage from './pages/homepage/Homepage';
// import components
import Footer from './components/footer/Footer';
import Header from './components/header/Header';
export default function App() {
const [authState, setAuthState] = useContext(AuthContext);
useEffect(() => {
console.log(authState); // prints *{}*
console.log(setAuthState); // prints *() => {}*
const token = localStorage.getItem('token');
const tokenIsExpired = parseInt(localStorage.getItem('tokenIsExpired'));
if (!tokenIsExpired && token.length) {
setAuthState({
userIsLoggedin: true,
fName: 'test fname',
lName: 'test lname',
userName: 'testname'
});
} else {
setAuthState({
userIsLoggedin: false,
fName: '',
lName: '',
userName: ''
});
}
if (tokenIsExpired) {
localStorage.setItem('token', '');
}
}, [authState, setAuthState]);
return (
<Router>
<AuthContextProvider value={[authState, setAuthState]}>
<div className='App'>
<Header />
<Switch>
<Route exact path='/'>
<Homepage />
</Route>
</Switch>
<Footer />
</div>
</AuthContextProvider>
</Router>
);
}
AuthContext.js
import React, { useState, createContext } from 'react';
const AuthContext = createContext([{}, () => {}]);
const AuthContextProvider = (props) => {
const [authState, setAuthState] = useState({
userIsLoggedin: false,
fName: '',
lName: '',
userName: ''
});
return (
<AuthContext.Provider value={[authState, setAuthState]}>
{props.children}
</AuthContext.Provider>
);
};
export { AuthContext, AuthContextProvider };
UseAuthCOntext.js
import { useContext } from 'react';
import { AuthContext } from './AuthContext';
const useAuthContext = () => {
const [authState, setAuthState] = useContext(AuthContext);
const login = (loginDetails) => {
setAuthState({
userIsLoggedin: true,
fName: 'test fname',
lName: 'test lname',
userName: 'testname'
});
};
const logout = () => {
setAuthState({
userIsLoggedin: false,
fName: '',
lName: '',
userName: ''
});
};
return { login, logout };
};
export default useAuthContext;
Header.js
// import installed dependencies
import React, { useContext, useEffect } from 'react';
// import components
import LoggedOutHeader from './logged-out-header/LoggedOutHeader';
import LoggedInHeader from './logged-in-header/LoggedInHeader';
// import custom contexts
import { AuthContext } from '../../contexts/auth/AuthContext';
const Header = () => {
const [authState, setAuthState] = useContext(AuthContext);
console.log(authState); //prints *{userIsLoggedin: false, fName: "", lName: "", userName: ""}*
console.log(setAuthState); //prints *ƒ dispatchAction(fiber, queue, action) {...*
const header = authState.isUserLoggedIn ? (
<LoggedInHeader />
) : (
<LoggedOutHeader />
);
return header;
};
export default Header;
You could use the context provider inside index.js.
ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById('root')
)
You are passing value to AuthContextProvider, which seems like the value you want to use, and you don't use it.
// value not used inside `AuthContextProvider`
<AuthContextProvider value={[authState, setAuthState]}>
It should be:
const AuthContextProvider = (props) => {
return (
<AuthContext.Provider value={props.value}>
{props.children}
</AuthContext.Provider>
);
};
Your are using the context in the App component, which is not wrapped within AuthContextProvider. In that case the useContext call in the App component will not return the value provided to AuthContextProvider but instead it'll return the "default" values provided to the createContext call.
You need to defer those logic in the App component to a children component within AuthContextProvider.
See note from createContext api:
The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing undefined as a Provider value does not cause consuming components to use defaultValue.
I'm new to redux and trying to fetch content from my BackEnd API. For some reason the action I call does not reach the reducer (It's not even executed). I first thought it was because it couldn't access the store since it is has a parent component but my Provider is well configured and there is another component at the same level, and just after i started thinking it was a problem with my dispatch but honestly i don't know. I have attached the code I feel is relevant and any contributions would be highly appreciated.
actions/viewers.js
import axios from 'axios';
import { VIEWERS_LOADED, VIEWERS_ERROR } from './types';
export const loadData = async (body, http) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
try {
const res = await axios.post(
http,
body,
config
);
return res.data;
} catch (error) {
console.log(error);
}
};
export const extractConcurrentViewers = (from, to, aggregate) => async dispatch => {
console.log("CONCURRENT VIEWERS");
const body = {
session_token: localStorage.token,
from,
to,
};
try {
let aggregateConcur = null;
const graphConccur = await loadData(body, 'http://localhost:5000/audience');
console.log('extractViews -> res_1', graphConccur);
if (aggregate !== null) {
body.aggregate = aggregate
aggregateConcur = await loadData(body, 'http://localhost:5000/audience');
}
console.log('extractaggregateViewers -> res_2', aggregateConcur);
dispatch({
type: VIEWERS_LOADED,
payload: {
graphConccur,
aggregateConcur
},
});
} catch (error) {
console.log(error);
dispatch({
type: VIEWERS_ERROR,
});
}
}
reducers/viewers.js
import {
VIEWERS_LOADED,
VIEWERS_ERROR,
} from '../actions/types';
const initialState = {
session_token: localStorage.getItem('token'),
concurrence: null,
aggConcurrence: null,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case VIEWERS_LOADED:
return {
...state,
...payload,
concurrence: payload.graphConccur.audience,
aggConcurrence: payload.aggregateConcur.audience,
};
case VIEWERS_ERROR:
return {
...state,
concurrence: null,
aggConcurrence: null,
};
default:
return state;
}
}
reducer/index.js
import {combineReducers} from 'redux';
import alert from './alert';
import auth from './auth'
import profile from './profile'
import chart from './chart'
import viewers from './viewers'
export default combineReducers({
alert,
auth,
profile,
chart,
viewers
});
App.js
import React, { useEffect } from 'react';
import Navbar from './components/layout/Navbar';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Landing from './components/layout/Landing';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import Alert from './components/layout/Alert';
import Dashboard from './components/dashboard/Dashboard';
import PrivateRoute from './components/routing/PrivateRouting';
import { Provider } from 'react-redux';
import store from './store';
import { loadUser } from './actions/auth';
import setAuthToken from './utils/setAuthToken'
import './App.css';
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
useEffect(() => {
store.dispatch(loadUser())
}, []);
return (
<Provider store={store}>
<Router>
<Navbar />
<Route exact path='/' component={Landing} />
<section className='container'>
<Alert />
<Switch>
<Route exact path='/login' component={Login} />
<Route exact path='/register' component={Register} />
<PrivateRoute exact path='/dashboard' component={Dashboard} />
</Switch>
</section>
</Router>
</Provider>
);
};
export default App;
This is where the function extractConcurrentViewers is to be called and the component supposed to use that is <Concurrent concurrence={concurrence}/> and what is really weird about is that the component just above it is implemented almost the same way but it's working.
import React, { useEffect, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import BandWidth from './BandWidth';
import Concurrent from './Concurrent';
import { extractCurrentClient } from '../../actions/profile';
import { extractchartData } from '../../actions/chart';
import { extractConcurrentViewers } from '../../actions/viewers';
const Dashboard = ({
extractCurrentClient,
extractchartData,
auth: { user },
profile: { profile, loading },
chart: { cdn, p2p, maxSum, maxCdn },
viewers: {concurrence}
}) => {
useEffect(() => {
extractCurrentClient();
extractchartData('max', 1585834831000, 1589118031000);
extractConcurrentViewers(1585834831000, 1589118031000);
}, []);
return loading && profile === null ? (
<Spinner />
) : (
<Fragment>
<h1 className='large text-primary'>Streaming</h1>
<p className='lead'>
<i className='fas fa-chart-line'></i>
Welcome {user && user.lname}
</p>
<BandWidth cdn={cdn} p2p={p2p} maxSum={maxSum} maxCdn={maxCdn} />
{/* <Concurrent concurrence={concurrence}/> */}
</Fragment>
);
};
Dashboard.propTypes = {
extractCurrentClient: PropTypes.func.isRequired,
extractchartData: PropTypes.func.isRequired,
extractConcurrentViewers: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
profile: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
profile: state.profile,
chart: state.chart,
viewers: state.viewers,
});
export default connect(mapStateToProps, {
extractCurrentClient,
extractchartData,
extractConcurrentViewers
})(Dashboard);
store.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
You mapped extractConcurrentViewers to props in connect but did not add it to the destructured props object. Since they share the same name, that means is you're calling your action creator without it being bound to dispatch, so it will not be delivered to your reducers.
const Dashboard = ({
extractCurrentClient,
extractchartData,
auth: { user },
profile: { profile, loading },
chart: { cdn, p2p, maxSum, maxCdn },
viewers: {concurrence},
extractConcurrentViewers // <-- add this
}) => {
Personally I don't destructure my props and this is one reason. I prefer the code to be explicit about where values and functions are coming from props.extractConcurrentViewers . But that's my preference.
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 have a App component where user login into the application and the remaining components must verify auth guard before rendering, otherwise redirect to login page i.e. App component.
I am trying to pass the state variable from App component to the child components via a PrivateRouter as my auth guard. But its not working. Before this i have tried also using react-router v4 to use render inside the route.
App.js
import React, { Component } from 'react';
import axios from 'axios';
import history from './History';
import './App.css';
class App extends Component {
constructor(){
super();
this.state = {
username: '',
password: '',
authenticated: false
};
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.setState({
authenticated : true,
});
history.push({pathname: '/home', state: { detail: response.data }});
history.go('/home');
}).catch(function(error) {
this.setState({
authenticated : false,
});
console.log('Error on Authentication' + error);
});
}
handleUserChange(event){
this.setState({
username : event.target.value,
});
}
handlePasswordChange = event => {
this.setState({
password: event.target.value
});
}
render() {
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>
);
}
}
export default 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 {
Router,
Redirect,
Route,
Switch
} from "react-router-dom";
import Home from './Home';
import User from './User';
import history from './History';
ReactDOM.render(
<Router history={history}>
<Switch>
<Route path="/" exact component={App} />
<PrivateRoute path="/home" component={Home} />
<PrivateRoute path="/user" component={User} />
</Switch>
</Router>,
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;
How can pass state variable authenticated to PrivateRouter or is there any better way of doing this?