When trying to use auth context in the App.js file it shows undefined. Please help me out in finding the mistake i am doing. Thank you for the help.
App.js
import React, { Fragment, useContext, useEffect } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Navbar from "./components/layout/Navbar";
import Home from "./components/pages/Home";
import About from "./components/pages/About";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
import Alerts from "./components/layout/Alerts";
import setAuthToken from "../src/utils/setAuthToken";
//PRIVATE ROUTE
import PrivateRoute from "./components/routing/PrivateRoute";
//CONTEXT IMPORTS
import ContactState from "./context/contact/ContactState";
import AuthState from "./context/auth/AuthState";
import AuthContext from "./context/auth/authContext";
import AlertState from "./context/alerts/AlertState";
import "./App.css";
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
const authContext = useContext(AuthContext);
useEffect(() => {
authContext.loadUser();
//eslint-disable-next-line
});
return (
<AuthState>
<ContactState>
<AlertState>
<Router>
<Fragment>
<Navbar />
<div className='container'>
<Alerts />
<Switch>
<PrivateRoute exact path='/' component={Home} />
<Route exact path='/about' component={About} />
<Route exact path='/register' component={Register} />
<Route exact path='/login' component={Login} />
</Switch>
</div>
</Fragment>
</Router>
</AlertState>
</ContactState>
</AuthState>
);
};
export default App;
Also, in Home.js i am using context the same way but there shows no error in this file and the app runs perfectly.
Home.js
import React, { useContext, useEffect } from "react";
import Contacts from "../contact/Contacts";
import ContactForm from "../contact/ContactForm";
import ContactFilter from "../contact/ContactFilter";
import AuthContext from "../../context/auth/authContext";
const Home = () => {
const authContext = useContext(AuthContext);
useEffect(() => {
authContext.loadUser();
//eslint-disable-next-line
});
return (
<div className="grid-2">
<div>
<ContactForm />
</div>
<div>
<ContactFilter />
<Contacts />
</div>
</div>
);
};
export default Home;
authContext.js
import { createContext } from "react";
const authContext = createContext();
export default authContext;
authReducer.js
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
AUTH_ERROR,
USER_LOADED,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
CLEAR_ERRORS,
} from "../types";
const authReducer = (state, action) => {
switch (action.type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem("token", action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
case LOGOUT:
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: null,
loading: false,
user: null,
error: action.payload,
};
default:
return state;
}
};
export default authReducer;
authState.js
import React, { useReducer } from "react";
import axios from "axios";
import setAuthToken from "../../utils/setAuthToken";
import AuthContext from "./authContext";
import authReducer from "./authReducer";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
CLEAR_ERRORS,
} from "../types";
//CREATE INITIAL STATE
const AuthState = (props) => {
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
user: null,
loading: true,
error: null,
};
//STATE ALLOWS US TO USE ANYTHING WE PUT IN THE STATE
//DISPATCH ALLOWS US TO DISPATCH OBJECTS,ACTIONS,METHODS OR ANYTHING TO REDUCER
const [state, dispatch] = useReducer(authReducer, initialState);
//ACTIONS
//LOAD USER - WHICH IS GOING TO TAKE CARE OF WHICH USER IS LOGGED AND ITS GOING TO HIT THAT AUTH ENDPOINT AND GET THE USER DATA
const loadUser = async () => {
// LOAD TOKEN INTO GLOBAL HEADERS
setAuthToken(localStorage.token);
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
//REGISTER USER - WHICH SIGNS THE USER UP AND GETS A TOKEN BACK
const register = async (formData) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const res = await axios.post("api/user", formData, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
loadUser();
} catch (err) {
dispatch({
type: REGISTER_FAIL,
payload: err.response.data.msg,
});
}
};
//LOGIN USER - WHICH WILL LOG THE USER IN AND GET THE TOKEN
const login = async (formData) => {
const config = {
headers: {
"Content-Types": "application/json",
},
};
try {
const res = await axios.post("/api/auth", formData, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
loadUser();
} catch (err) {
dispatch({
type: LOGIN_FAIL,
payload: err.response.data.msg,
});
}
};
//LOGOUT - WHICH WILL DESTROY THE TOKEN AND JUST CLEAR EVERYTHIN UP
const logout = () => dispatch({ type: LOGOUT });
//CLEAR_ERRORS - TO CLEAR OUT ANY ERRORS IN THE STATE
const clearErrors = () =>
dispatch({
type: CLEAR_ERRORS,
});
//BY SURROUNDING THE COMPONENT IN THE SPECIFIC PROVIDER TAGS WE GET ACCESS TO THE STATE AND FUCNTIONS OF THE PROVIDER
return (
<AuthContext.Provider
value={{
token: state.token,
isAuthenticated: state.isAuthenticated,
loading: state.loading,
user: state.user,
error: state.error,
register,
loadUser,
login,
logout,
clearErrors,
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default AuthState;
The above image shows the folder structure
You might need to wrap your App.js file in an AuthContext provider, probably in your index.js file.
ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById('root')
)
Related
I am working on a signup React app that uses redux. Everything other thing works quite right with the exception of state update.
I've gone through several recommendations already given here and I don't seem to see what's wrong with the code.
The authAction.js
import { API_URL } from '../../../constants/constants';
const LOGIN_SUCCESSFUL = 'LOGIN_SUCCESSFUL';
const LOGIN_LOADING = 'LOGIN_LOADING';
const LOGIN_FAILED = 'LOGIN_FAILED';
const login = values => {
let url = API_URL + 'login';
return async (dispatch) => {
dispatch({
type: LOGIN_LOADING
})
const response = await fetch (url, {
method: 'POST',
body: JSON.stringify(values),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
if(response.status >=200 && response.status <= 299)
{
sessionStorage.setItem('_token', data.data.jwt)
dispatch({
type: LOGIN_SUCCESSFUL,
payload: {
isAuthenticated: true,
jwt: data.data.jwt ?? ''
}
});
}
dispatch({
type: LOGIN_FAILED,
payload: {
isAuthenticated: false,
jwt: '',
message: data?.message ?? 'Authentication failed.'
}
})
}
}
export { login, logout };
authReducer.js
const LOGIN_SUCCESSFUL = 'LOGIN_SUCCESSFUL';
const LOGIN_FAILED = 'LOGIN_FAILED';
const LOGIN_LOADING = 'LOGIN_LOADING';
const initialState = {
jwt: '',
isAuthenticated: false,
message: '',
loading: false,
error: false,
};
const authReducer = (state = initialState, action) => {
if(action.type === LOGIN_LOADING)
{
return {
...state,
message: 'Authenticating...',
loading: true
}
}
if(action.type === LOGIN_SUCCESSFUL)
{
return {
...state,
isAuthenticated: true,
jwt: action.payload.jwt,
message: action.payload.message,
laoding: false,
error: true
}
}
if(action.type === LOGIN_FAILED)
{
return {
...state,
jwt: '',
isAuthenticated: false,
loading: false
};
}
return initialState;
}
export default authReducer;
rootReducer.js where I combined other reducers
import { combineReducers } from "redux";
import userReducer from "./users/userReducer";
import authReducer from './users/authReducer';
import signupReducer from './users/signupReducer';
import postReducer from './postReducer'
const rootReducer = combineReducers({
user: userReducer,
auth: authReducer,
signup: signupReducer,
posts: postReducer
});
export default rootReducer;
signup.js that handles the view
import {useFormik } from 'formik';
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { Link, Navigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import * as Yup from 'yup';
import logo from '../../assets/img/logo1.jpeg';
import Error from '../../components/forms/Error';
import LandingLayout from '../layouts/landing';
import signup from '../../redux/actions/users/signupActions';
import Toast from '../../components/alerts/Toast';
const Signup = () => {
const {loading, error, status} = useSelector(state => state.signup);
const dispatch = useDispatch();
useEffect(()=>{
if(status)
{
setTimeout(() => {
return <Navigate to='/login' />
}, 2000);
}
}, [dispatch, status])
...
onSubmit: (values) => {
dispatch(signup(values));
}
...
export default Signup;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './redux/reducers/rootReducer';
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store = {store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
when a log the response from the API call, I get the expected response but nothing is effected on the UI.
Well, it appears that the error was coming from somewhere else. Just as previously stated, everything I did was quite right aside the fact that one of the reducers - userReducer - included in the rootReducer had its action creator returning the wrong payload.
I commented that out and everything else worked.
However, should subtle bug from one reducer affect the entire workings of the store?
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.
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'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')
I am trying to route the user to specific route according to user's role. I am using redux as state management tool. I am trying to access the user object from auth state to route the user.
When I login to the system below are the logical steps that take user to route.
Login to the system
Authenticate and load the user
Redirect user to route according to role
I am trying to access the user.role in DashBoard component which is causing the error.
Please help me understand the bug.
1. Action to login to system
export const login = (email, password) => async dispatch => {
const config ={
headers:{
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({email, password});
try {
const res = await axios.post('/api/v1/midasUsers/login',body,config);
dispatch({
type:LOGIN_SUCCESS,
payload:res.data
});
dispatch(loadUser());
} catch (err) {
console.log(err)
const errors = err.response.data.errors;
if(errors){
errors.forEach(error =>dispatch(setAlert(error.msg,'danger')));
}
dispatch({
type:LOGIN_FAIL
})
}
}
2.loaduser to localstorage to authenticate:
export const loadUser = () => async dispatch => {
console.log("I am inside loaduser");
if(localStorage.token){
setAuthToken(localStorage.token)
}
try {
const res = await axios.get('/api/v1/midasUsers/auth');
dispatch({
type: USER_LOADED,
payload:res.data
})
} catch (err) {
dispatch({
type: AUTH_ERROR
})
}
}
3. DashBord.js - Component to route the user
import React,{useEffect} from 'react';
import {Redirect} from 'react-router-dom';
import { connect} from 'react-redux';
import PropTypes from 'prop-types';
//import store from '../../store';
//import {loadUser} from '../../action/auth';
const Dashboard = ({auth:{user,loading,isAuthenticated}}) => {
if(user.role === 'admin'){
return <Redirect to='/adminLandingPage'/>
}
}
Dashboard.propTypes = {
auth:PropTypes.object.isRequired
}
const mapStateToProps = state => ({
auth : state.auth
})
export default connect(mapStateToProps,{})(Dashboard);
I am calling loadUser everytime App.js is mounted the same function after LOGIN_SUCCESS action calls this route
GET /%3Canonymous%3E
Please help me understand the issue
auth.reducer
import {REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT} from '../action/types';
const initialState = {
token : localStorage.getItem('token'),
isAuthenticated : null,
loading: true,
user:null
}
export default function(state= initialState, action){
const {type, payload} = action;
switch (type) {
case USER_LOADED:
return{
...state,
isAuthenticated:true,
loading: false,
user:payload
}
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return{
...state,
...payload,
isAuthenticated:true,
loading:false,
}
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
case LOGOUT:
localStorage.removeItem('token');
return{
...state,
token:null,
isAuthenticated:false,
loading:false
}
default:
return state;
}
}
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;
index.js in reducers folder
import { combineReducers } from 'redux';
import alert from './alert';
import auth from './auth';
export default combineReducers ({
alert,
auth
});
import React, { Fragment, useState } from 'react';
import { Link, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { login } from '../../action/auth';
const Login = ({ login, isAuthenticated, user }) => {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = async e => {
e.preventDefault();
login(email, password);
};
//Redirect if logged in
if(isAuthenticated){
//console.log(user.role);
return <Redirect to ="/dashboard"/>
}
return (
<Fragment>
<h1 className='large text-primary'>Sign In</h1>
<p className='lead'>
<i className='fas fa-user' /> Sign Into Your Account
</p>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-group'>
<input
type='email'
placeholder='Email Address'
name='email'
value={email}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input
type='password'
placeholder='Password'
name='password'
value={password}
onChange={e => onChange(e)}
minLength='6'
/>
</div>
<input type='submit' className='btn btn-primary' value='Login' />
</form>
<p className='my-1'>
Don't have an account? <Link to='/register'>Sign Up</Link>
</p>
</Fragment>
);
};
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated:PropTypes.bool
};
const mapStateToProps = state =>({
isAuthenticated : state.auth.isAuthenticated,
user:state.auth.user
})
export default connect(mapStateToProps,
{ login }
)(Login);
I have this login component which redirects user to dashbaord and at dashbaord i am not able to access the user.role from state