React Context: Cannot access 'AuthContext' before initialization - javascript

The issue
I am trying to use Context to pass GitHub API authorised user data from the login page the the home page so that the user information can be displayed (avatar image, name etc.)
However whenever I try to access the context from my class component Home in Home.js I am told Cannot access 'AuthContext' before initialization.
My Code
Login.js
import React, { useState, useEffect, useContext } from "react";
import { Redirect } from "react-router-dom";
import Styled from "styled-components";
import { AuthContext } from "../App";
export default function Login() {
const { state, dispatch } = useContext(AuthContext);
const [data, setData] = useState({ errorMessage: "", isLoading: false });
const { client_id, redirect_uri } = state;
useEffect(() => {
const url = window.location.href;
const hasCode = url.includes("?code=");
if (hasCode) {
const newUrl = url.split("?code=");
window.history.pushState({}, null, newUrl[0]);
setData({ ...data, isLoading: true });
const requestData = {
code: newUrl[1]
};
const proxy_url = state.proxy_url;
fetch(proxy_url, {
method: "POST",
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
dispatch({
type: "LOGIN",
payload: { user: data, isLoggedIn: true }
});
})
.catch(error => {
setData({
isLoading: false,
errorMessage: "Sorry! Login failed: " + error
});
});
}
}, [state, dispatch, data]);
if (state.isLoggedIn) {
return <Redirect to="/" />;
}
return (
<Wrapper>
<section className="container">
<div className="card">
<span className="errorMessage">{data.errorMessage}</span>
<div className="login-container">
{data.isLoading ? (
<div className="loader-container">
<div className="loader"></div>
</div>
) : (
<>
{
}
<a
className="login-link"
href={`https://github.com/login/oauth/authorize?scope=user&client_id=${client_id}&redirect_uri=${redirect_uri}`}
onClick={() => {
setData({ ...data, errorMessage: "" });
}}
>
<span className="btn">Login with GitHub</span>
</a>
</>
)}
</div>
</div>
</section>
</Wrapper>
);
}
const Wrapper = Styled.section`
.container {
background-color: #333;
}
`;
Home.js
import React from "react";
import { Redirect } from "react-router-dom";
import Styled from "styled-components";
import { AuthContext } from "../App";
class Home extends React.Component {
constructor() {
super()
this.state = {
'items': []
}
}
render() {
const { state, dispatch } = this.context
if (!state.isLoggedIn) {
return <Redirect to="/login" />;
}
const { avatar_url, name, public_repos, followers, following } = state.user
const handleLogout = () => {
dispatch({
type: "LOGOUT"
});
}
return (
<Wrapper>
<div className="container">
<div>
<div className="profileBox">
<button className="btn" onClick={()=> handleLogout()}>Logout</button>
<img className="avatarImage" src={avatar_url} alt="Avatar"/>
<span>{name}</span>
</div>
</div>
</div>
</Wrapper>
)
}
}
const Wrapper = Styled.section`
.content {
background-color: lightgray;
}
`;
Home.contextType = AuthContext
export default Home
app.js
import React, { createContext, useReducer } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import Login from "./components/Login";
import { initialState, reducer } from "./store/reducer";
import Styled from "styled-components";
export const AuthContext = createContext();
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AuthContext.Provider
value={{
state,
dispatch
}}
>
<Router>
<Switch>
<Route path="/login" component={Login}/>
<Route path="/" component={Home}/>
</Switch>
</Router>
</AuthContext.Provider>
);
}
export default App;
What I have tried
I have attempted to move the Home.contextType = AuthContext into the render() however becuase the render is called twice (I think it is related to this) on the first run it is an empty object, state and dispatch are not there. This leads to errors relating to isLoggedIn and other context related items being "undefined" so I did not think this would be the appropriate solution.
I have also tried using statis contextType = AuthContext both before the constructor declaration and after but neither of these worked for me.
I have also attempted to use AuthContext.Consumer but with no avail as when this we put in place within the render() I was then not able to access any other the data. But then I am happy to admit React is new to me and therefore I may have misunderstood how Consumer works.
Any help is greatly appreciated.

Related

Nextjs TypeScript useContext through the pages

Edit:
I was not using
import Link from 'next/link';
in my Header.tsx component.
Now it works.
Don't know what I am doing wrong right here.
I try to make global state (to indicate if user is logged in or not) that just flows through the pages and I try to do it with react's hook useContext.
It is not working like how I would like to make it work. When I toggleLogged and go to another page, the context has default value and not the changed one.
I think the problem I am facing is something really small or a fundemantal thing that I just can't see.
Here is how my UserContext.ts file looks:
import { createContext, useContext } from 'react';
type userContextType = {
isLogged: boolean;
toggleLogged: () => void;
};
const userContextDefault: userContextType = {
isLogged: false,
toggleLogged: () => {},
};
export const UserContext = createContext(userContextDefault);
export function useUserContext() {
return useContext(UserContext);
}
Here is my Layout.tsx:
import React, { useState } from 'react';
import Header from './Header';
const Layout = (props: any) => {
const { children } = props;
return (
<div className='container'>
<Header />
{children}
</div>
);
};
export default Layout;
And lastly here is my _app.tsx:
import type { AppContext, AppProps } from 'next/app';
import Layout from '../components/Layout';
import { useState } from 'react';
import { UserContext } from '../components/UserContext';
import '../styles/globals.css';
const MyApp = ({ Component, pageProps }: AppProps) => {
const [isLogged, setIsLogged] = useState(true);
const toggleLogged = () => {
setIsLogged((isLogged) => !isLogged);
};
return (
<UserContext.Provider value={{ isLogged, toggleLogged }}>
<Layout>
<Component {...pageProps} />;
</Layout>
</UserContext.Provider>
);
};
export default MyApp;
Thanks for any help in advance.
You need to return the provider in the context, and then reference the instance in _app.js.
Here's my AuthContext (as an example). Don't worry about the specific code, but use my implementation as the foundation, and you'll be up and running in no time!
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } from '../firebase'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
My _app.js file:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}

Action not triggered in React/Redux

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.

What is the correct way of redirecting after successful post request in React?

I'm new to React and I am setting up a small project. I am using a NodeJS server that answers to my request and I am trying to redirect the user after an successful login. I dispatch an action and update my redux store with the user information, that is working correctly. But when I try to redirect him I either get no errors and nothing happens or the URL changes but no component renders.
BTW in LoginForm.js I was trying to return a redirect after many fails by trying to add a callback with history object to my action.
So here is my code
App.js
import React, { Component } from 'react';
import LoginPage from './login/LoginPage';
import LandingPage from './landingpage/landing.page';
import ProtectedRoute from './protected/ProtectedRoute';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import PageNotFound from './common/pageNotFound';
class App extends Component {
render() {
return (
<Router >
<Switch>
<Route path="/login" component={() => <LoginPage />} />
<ProtectedRoute path="/" component={LandingPage} />
<Route component={() => <PageNotFound />} />
</Switch>
</Router>
)
}
}
export default App;
LoginPage.js
import React, { Component } from 'react'
import LoginForm from './LoginForm';
import PropTypes from 'prop-types'
import { connect } from 'react-redux';
import { login } from '../../actions/authActions';
import { withRouter } from "react-router";
class LoginPage extends Component {
render() {
const { login, userLoading, history } = this.props;
return (
<div>
<h1>Login in here</h1>
<LoginForm login={login} isLoading={userLoading} history={history} />
</div>
)
}
}
LoginPage.propTypes = {
login: PropTypes.func.isRequired
}
function mapStateToProps(state) {
return {
userLoading: state.auth.isLoading
}
}
export default connect(mapStateToProps, { login })(withRouter(LoginPage));
LoginForm.js
import React, { Component } from 'react'
import TextInput from '../common/TextInput';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
redirect: false,
}
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();
this.setState({ error: null });
this.props.login(this.state);
this.setState({
redirect: true
})
}
render() {
const { isLoading, isAuth } = this.props;
const { redirect } = this.state;
console.log(redirect, isAuth)
if (redirect && isAuth) {
return <Redirect to="/" />
}
else {
return (
<form onSubmit={this.handleSubmit}>
<TextInput type="email" name="email" label="Email" onchange={this.handleChange} />
<TextInput type="password" name="password" label="Password" onchange={this.handleChange} />
{isLoading && <p>We are loggin you in</p>}
<button disabled={isLoading} type="submit">Log in</button>
</form>
)
}
}
}
const mapStateToProps = (state) => {
return {
isAuth: state.auth.isAuthenticated
}
}
LoginForm.propTypes = {
login: PropTypes.func.isRequired
}
export default connect(mapStateToProps)(LoginForm);
authActions.js
import {
LOGIN,
LOGIN_SUCCESS,
LOGIN_FAILED,
USER_LOADING,
USER_LOADED,
AUTH_ERROR,
REGISTER_SUCCESS,
REGISTER_FAIL,
} from '../constants';
export function login(payload) {
return dispatch => {
dispatch({ type: USER_LOADING })
setTimeout(function () {
return fetch('http://localhost:3000/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}).then(
(res) => {
dispatch({ type: LOGIN_SUCCESS })
return res.json();
},
(err) => dispatch({ type: LOGIN_FAILED })
).then((data) => {
dispatch({
type: USER_LOADED,
payload: {
token: data.token,
userid: data.userID
}
})
});
}, 1000);
}
}
Since your LoginForm is wrapped with withRouter your can use this.props.history.pushState('/next-route')

TypeError: Cannot read property 'token' of undefined error in reactjs login page

I've been trying to create a login page and I'm getting this error TypeError: Cannot read property 'token' of undefined. I'm not sure why I'm getting this error as I'm fairly new. I wanted to create protected routes for my dashboard page so I tried to use react auth hook context for creating the protected routes. Am I getting this error cause there are no token stored in backend or does it set the token in frontend itself? I have no idea of token or authorization so I'm really confused as to where to look for it. Should I ask the backend guy to store a token variable in the backend or I can do something myself in the frontend? My pages looks like this:
Login.js
import React, { Component, useContext, useState } from "react";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import AppBar from "material-ui/AppBar";
import RaisedButton from "material-ui/RaisedButton";
import TextField from "material-ui/TextField";
import { Link, Redirect } from "react-router-dom";
import { AuthContext } from "./auth";
import "./App.css";
const Login = () => {
const [isLoggedIn, setLoggedIn] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { setToken } = useContext(AuthContext);
const performLogin = async e => {
e.preventDefault();
var body = {
password: password,
email: email
};
console.log(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options);
const text = await response.text();
console.log(response.token);
if (text === "redirect") {
setToken(response.data.token);
setLoggedIn(true);
} else if (text === "verifyemail") {
this.props.history.push(`/verifyOtp/${this.state.email}`);
} else {
console.log("login failed");
window.alert("login failed");
}
} catch (error) {
console.error(error);
}
};
if (isLoggedIn) {
this.props.history.push(`/dashboard`);
}
return (
<>
<div className="loginForm">
<MuiThemeProvider>
<TextField
hintText="Enter your Email"
floatingLabelText="Email"
onChange={(event, newValue) => setEmail(newValue)}
/>
<br />
<TextField
type="password"
hintText="Enter your password"
floatingLabelText="password"
onChange={(event, newValue) => setPassword(newValue)}
/>
<br />
<RaisedButton
label="Submit"
primary={true}
style={style}
onClick={performLogin}
/>
</MuiThemeProvider>
</div>
</>
);
};
const style = {
margin: 15
};
export default Login;
App.js
import React, { useState } from "react";
import {
BrowserRouter as Router,
Redirect,
Switch,
Route,
Link
} from "react-router-dom";
import "./App.css";
import Login from "./login";
import Dashboard from "./dashboard";
import { AuthProvider } from "./auth";
import PrivateRoute from "./PrivateRoute";
function App(props) {
return (
<>
<AuthProvider>
<Router>
<div className="container">
<nav className="navbar navbar-expand-lg navheader">
<div className="collapse navbar-collapse">
<ul className="navbar-nav mr-auto">
<li className="nav-item">
<Link to={"/Login"} className="nav-link">
Login
</Link>
</li>
</ul>
</div>
</nav>
<br />
<Switch>
<Route exact path="/login" component={Login} />
<Route render={() => <Redirect to="/dashboard" />} />
<PrivateRoute exact path="/dashboard" component={Dashboard} />
</Switch>
</div>
</Router>
</AuthProvider>
</>
);
}
export default App;
dashboard.js
import React, { useState, useContext } from "react";
import { Redirect, withRouter } from "react-router-dom";
import { AuthContext } from "./auth";
const Dashboard = () => {
const { setToken } = useContext(AuthContext);
function logOut() {
setToken();
}
return (
<>
<div>hello</div>
<button onClick={logOut}>Logout</button>
</>
);
};
export default withRouter(Dashboard);
edit: auth.js
import React, { createContext, useReducer, useEffect } from "react";
let reducer = (token, newToken) => {
if (newToken === null || newToken === undefined) {
localStorage.removeItem("token");
return initialState;
}
return newToken
};
const initialState = null
const localState = JSON.parse(localStorage.getItem("token"));
const AuthContext = createContext();
function AuthProvider(props) {
const [token, setToken] = useReducer(reducer, localState || initialState);
useEffect(() => {
localStorage.setItem("token", JSON.stringify(token));
}, [token]);
return (
<AuthContext.Provider value={{ token, setToken }}>
{props.children}
</AuthContext.Provider>
);
}
export { AuthContext, AuthProvider };
My whole code can be found in this codesandbox. The auth.js and privateroute.js files are in the codesandbox.
Here I tested your api using axios. its works charm..
testingMethod = async () => {
const response = await axios.post(
"http://15.206.42.0:5000/api/authenticate",
{
email: "hehacet424#mail3tech.com",
password: "just-fordemo10"
},
{
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
Accept: "application/json"
}
}
);
console.log("response", response);
};

call mapDispatchToProps inside axios success not working

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.

Categories