React State Undefined - reactjs

The problem is that when I first Logged-in, The userId Variable on renderList() or the this.props.user will always be null. it will work when I refreshed it. I tried checking it on the first line of renderList function but it seems it will always be null after I logged in. I even tried to dispatch the fetchBlog actions before redirecting after logging in successfully.
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Card, Button } from 'react-bootstrap';
import { fetchBlogs, deleteBlog } from '../../actions';
class FetchBlogs extends React.Component{
componentDidMount(){
this.props.fetchBlogs();
}
renderButtons(blog, userId){
if(blog._id = userId){
return (
<div>
<Button as={Link} to={`/blogs/edit/${blog._id}`} className="mx-2" variant="outline-warning" size="md">Edit</Button>
<Button onClick={() => {
this.props.deleteBlog(blog._id)
}}
className="mx-2"
variant="outline-danger"
size="md">
Delete
</Button>
</div>
);
}
return '';
}
renderList(){
const userId = this.props.user && this.props.user._id;
return this.props.blogs.map(blog => {
return (
<Card className="m-2" key={blog.title}>
<Card.Body>
<Card.Title as={Link} to={`/blogs/${blog._id}`}>{blog.title}</Card.Title>
<Card.Text>{blog.content}</Card.Text>
</Card.Body>
<div className="mb-2">
{this.renderButtons(blog, userId)}
</div>
</Card>
);
})
}
render(){
return (
<div>
<h2 className="m-2">My Blogs</h2>
{this.renderList()}
</div>
);
}
}
const stateToProps = state => {
return {
blogs: Object.values(state.blogs),
user: state.auth.user,
}
}
export default connect(stateToProps, { fetchBlogs, deleteBlog, })(FetchBlogs);
This is the code for my action login
export const login = formValues => async dispatch => {
const config = {
header: {
'Content-Type': 'application/json'
}
};
try{
const res = await portfolios.post('/auth/login', formValues, config)
dispatch({
type: 'LOGIN_SUCCESS',
payload: res.data
});
dispatch(fetchBlogs())
history.push('/blogs/all');
}catch(e){
dispatch({
type: 'LOGIN_FAILED',
});
history.push('/auth/login');
}
}
This is my Auth Reducer
const INITIAL_STATE = {
access: localStorage.getItem('access'),
isAuthenticated: null,
isLoading: false,
user: null,
}
const authReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case 'LOGIN_SUCCESS':
case 'REGISTER_SUCCESS':
localStorage.setItem("access", action.payload.token);
return {
...state,
access: action.payload.token,
user: action.payload.user,
isAuthenticated: true,
isLoading: false
};
case 'AUTH_ERROR':
case 'LOGIN_FAILED':
case 'LOGOUT_SUCCESS':
case 'REGISTER_FAIL':
localStorage.removeItem('access');
return {
...state,
user: null,
isAuthenticated: false,
isLoading: false,
}
default:
return state;
}
}
I don't know what am I missing. Ive checked and Searched some answers and I did check if the prop is null but still no changes

Related

React/Redux ReferenceError: Cannot access 'Action' before initialization

I have made a React site using redux which is successfully working on one component.
This is mapped to props and pulls data from redux using the fetchposts.
This looks like this.
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { fetchPosts, fetchItins } from "../../../actions/postActions";
import TravelAlerts from "./TravelAlerts//travelAlert";
import IntelligenceAlerts from "./IntelligenceAlerts/IntelligenceAlert";
import AllAlerts from "./AllAlerts/AllAlerts";
import "./Alerts.css";
class Alerts extends Component {
state = {
showAllAlerts: true,
showAllIntelligenceAlerts: false,
showAllTravellers: false,
currentPage: 1,
alertsPerPage: 20,
allAlerts: [],
dataFetched: false,
};
//usiing redux action which is mapped to this compoenent
componentDidMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<hr />
<div>
{this.state.showAllAlerts ? (
<>
<AllAlerts all={this.Sorter()} />
</>
) : (
<></>
)}
</div>
<>
{this.state.showAllTravellers ? (
<>
<></>
<TravelAlerts alerts={this.props.posts.travelAlerts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllIntelligenceAlerts ? (
<>
<IntelligenceAlerts
alerts={this.props.posts.intelligenceAlerts}
/>
</>
) : (
<></>
)}
</>
</div>
);
}
}
Alerts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.object.isRequired,
// newPost: PropTypes.object
};
const mapStateToProps = (state) => ({
posts: state.posts.items,
// newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(Alerts);
This works fine and does successfully get information. However when doing this (Which is essentially exactly the same it throws this error).
ReferenceError: Cannot access 'fetchItins' before initialization
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { fetchPosts, fetchItins } from "../../../actions/postActions";
import ItineraryNew from "./ItineraryNew/ItineraryNew";
import ItineraryAll from "./ItineraryAll/ItineraryAll";
import ItineraryArrivals from "./ItineraryArrivals/ItineraryArrivals";
import ItineraryDepatures from "./ItineraryDepatures/ItineraryDepatures";
class Itinerary extends Component {
state = {
showAllItins: true,
showAllItinsArrivals: false,
showAllItinsDepatures: false,
showAllItinsNew: false,
currentPage: 1,
alertsPerPage: 20,
};
//usiing redux action which is mapped to this compoenent
componentDidMount() {
this.props.fetchItins();
}
//navigation helper
DisableAlerts() {
this.setState({
showAllItins: false,
showAllItinsArrivals: false,
showAllItinsDepatures: false,
showAllItinsNew: false,
});
}
//pagination change page
handleClick(number) {
this.setState({
currentPage: number,
});
}
ToggleItin(name) {
this.DisableAlerts();
if (name === "All") {
this.setState({ showAllItins: true });
} else if (name === "Arrivals") {
this.setState({ showAllItinsArrivals: true });
} else if (name === "Depatures") {
this.setState({ showAllItinsDepatures: true });
} else if (name === "New") {
this.setState({ showAllItinsNew: true });
} else {
this.setState({ showAllItins: true });
}
}
render() {
{
console.log(this.props.posts);
}
return (
<div>
<button style={{ width: "18%" }} onClick={() => this.ToggleItin("All")}>
ALL Travel
</button>
<button
style={{ width: "18%" }}
onClick={() => this.ToggleItin("Arrivals")}
>
Arrivals
</button>
<button
style={{ width: "18%" }}
onClick={() => this.ToggleItin("Depatures")}
>
Depatures
</button>
<button style={{ width: "18%" }} onClick={() => this.ToggleItin("New")}>
New
</button>
<br />
<hr />
<>
{this.state.showAllItins ? (
<>
<ItineraryAll itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllItinsArrivals ? (
<>
<ItineraryArrivals itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllItinsDepatures ? (
<>
<ItineraryDepatures itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllItinsNew ? (
<>
<ItineraryNew itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
</div>
);
}
}
Itinerary.propTypes = {
fetchItins: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired,
// newPost: PropTypes.object
};
const mapStateToProps = (state) => ({
posts: state.itin.itins,
// newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchItins })(Itinerary);
So I thought I would try to completely replace the itinerary component with all the initial component so that the data is exactly the same and just change its name to itinerary(as first component worked perfectly).
However when I did this it errored
ReferenceError: Cannot access 'fetchPosts' before initialization
Which was interesting to me as it works fine within the other component and now the code is essentially exactly the same.
Below is the store/actions and reducer
reducer
import { FETCH_POSTS, NEW_POST, FETCH_ITINS } from "../actions/types";
const initialState = {
items: {},
item: {},
itins: {},
travelAlerts: [],
intelligenceAlerts: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
items: action.payload,
travelAlerts: action.payload.travelAlerts,
intelligenceAlerts: action.payload.intelligenceAlerts,
};
// case NEW_POST:
// return {
// ...state,
// item: action.payload,
// };
case FETCH_ITINS:
return {
...state,
itins: action.payload,
};
default:
return state;
}
}
store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
reducer
import Itinerary from "../components/SideNav/Itinerary/Itinerary";
import { FETCH_POSTS, NEW_POST, FETCH_ITINS } from "./types";
export const fetchPosts = () => (dispatch) => {
fetch(
"a url im using"
)
.then((res) => res.json())
.then((posts) =>
dispatch({
type: FETCH_POSTS,
payload: posts,
})
);
};
export const fetchItins = () => (dispatch) => {
fetch("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.json())
.then((itin) =>
dispatch({
type: FETCH_ITINS,
payload: itin,
})
);
};
export const createPost = (postData) => (dispatch) => {
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(postData),
})
.then((res) => res.json())
.then((post) =>
dispatch({
type: NEW_POST,
payload: post,
})
);
};
I would really appreciate any help on this as I am at a loss.

Component not rendering the updated state after deleting the post

I'm having some problems with deleting the post in my app. So, after deleting the post, the state should update and the component should re-render, right? So, after deleting my post, component re-renders with the same data. If I refresh, then only the updated data is shown on the page. For example, if I have 3 posts in my app when I delete ONE post, the component re-renders, but still it shows 3 posts. I don't know why this is happening.
Here's my code.
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
console.log("render called")
const { isFetchingUserPosts, userPosts } = this.props
console.log(isFetchingUserPosts, userPosts)
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { deletePost } from "../actions/userActions"
class Cards extends Component {
handleDelete = (_id) => {
this.props.dispatch(deletePost(_id))
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{border: "1px grey"}}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button onClick={() => {this.handleDelete(_id)}} className="button is-success">Delete</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return state
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
deletePost action
export const deletePost = (id) => {
return async dispatch => {
dispatch({ type: "DELETING_POST_START" })
try {
const res = await axios.delete(`http://localhost:3000/api/v1/posts/${id}/delete`)
dispatch({
type: "DELETING_POST_SUCCESS",
data: res.data
})
} catch(error) {
dispatch({
type: "DELETING_POST_FAILURE",
data: { error: "Something went wrong" }
})
}
}
}
userPosts reducer
const initialState = {
isFetchingUserPosts: null,
isFetchedUserPosts: null,
userPosts: [],
fetchingUserPostsError: null,
isDeletingPost: false,
isDeletedPost: false,
deletingError: false,
}
const userPosts = (state = initialState, action) => {
switch (action.type) {
case "FETCHING_USER_POSTS_START":
return {
...state,
isFetchingUserPosts: true,
fetchingUserPostsError: null,
}
case "FETCHING_USER_POSTS_SUCCESS":
return {
...state,
isFetchingUserPosts: false,
isFetchedUserPosts: true,
userPosts: action.data,
fetchingUserPostsError: null,
}
case "FETCHING_USER_POSTS_ERROR":
return {
...state,
isFetchingUserPosts: false,
isFetchedUserPosts: false,
fetchingUserPostsError: action.data.error,
}
case "DELETING_POST_START":
return {
...state,
isDeletingPost: true,
deletingError: null,
}
case "DELETING_POST_SUCCESS":
const filteredPostList = state.postList.filter((post) => post._id !== action.data._id)
return {
...state,
isDeletingPost: false,
isDeletedPost: true,
userPosts: filteredPostList,
deletingError: null,
}
case "DELETING_POST_ERROR":
return {
...state,
isDeletingPost: false,
deletingError: action.data.error,
}
default:
return state
}
}
export default userPosts
Delete post action needs to pass on id to the reducer upon success.
Delete post action
export const deletePost = (id) => {
return async dispatch => {
dispatch({ type: "DELETING_POST_START" })
try {
const res = await axios.delete(`http://localhost:3000/api/v1/posts/${id}/delete`)
dispatch({
type: "DELETING_POST_SUCCESS",
data: res.data,
id
})
} catch(error) {
dispatch({
type: "DELETING_POST_FAILURE",
data: { error: "Something went wrong" }
})
}
}
}
Access action.id in user posts reducer
case "DELETING_POST_SUCCESS":
return {
...state,
isDeletingPost: false,
isDeletedPost: true,
userPosts: state.postList.filter(post => post._id !== action.id),
deletingError: null,
}

Props not loaded on initial render from redux store

I am new to reactjs and need some help.
thanks for taking the time to solve my issue.
I am trying to get the data from props after authenticating a user from Signin page .
so when I get to home page the props does not give me the data in "auth" on initial render but automatically renders again and gives me the data.
I need the data on initial render or else the page crashes due to user data in the home page not being present
signin.js
class Signin extends Component {
state = { isSignedIn: false,
user: [] }
uiConfig = {
signInFlow: "popup",
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
],
callbacks: {
signInSuccess: () => false
}
}
componentDidMount = () => {
firebase.auth().onAuthStateChanged(user => {
this.setState({ isSignedIn: !!user,
user})
console.log("state user is", this.state.user)
console.log("user on firebase", user)
})
}
render(){
const { authError, auth } = this.props;
if (auth.uid) return <Redirect to='/home' />
const signingin = (
this.state.isSignedIn ? (
<span>
<h1>signedin</h1>
</span>
) : (
<StyledFirebaseAuth
uiConfig={this.uiConfig}
firebaseAuth={firebase.auth()}
/>
))`
return (
<div>{signingin}
</div>
)
}
}
const mapStateToProps = (state) => {
return{
authError: state.auth.authError,
auth: state.firebase.auth
}
}
const mapDispatchToProps = (dispatch) => {
return {
signIn: (creds) => dispatch(signIn(creds)),
signOut: () => dispatch(signOut())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Signin);
authActions.js
export const signIn = (credentials) => {
return (dispatch, getState, {getFirebase}) => {
const firebase = getFirebase();
firebase.auth()(
credentials.email,
credentials.password
).then(() => {
dispatch({ type: 'LOGIN_SUCCESS' });
}).catch((err) => {
dispatch({ type: 'LOGIN_ERROR', err });
});
}
}
export const signOut = () => {
return (dispatch, getState, {getFirebase}) => {
const firebase = getFirebase();
firebase.auth().signOut().then(() => {
dispatch({ type: 'SIGNOUT_SUCCESS' })
});
}
}
authReducer.js
const initState = {
authError: null
}
const authReducer = (state = initState, action) => {
switch(action.type){
case 'LOGIN_ERROR':
console.log('login error');
return {
...state,
authError: 'Login failed'
}
case 'LOGIN_SUCCESS':
console.log('login success');
return {
...state,
authError: null
}
case 'SIGNOUT_SUCCESS':
console.log('signout success');
return state;
default:
return state
}
};
export default authReducer;
Home.js
import React, { Component } from 'react';
import firebase from 'firebase/app'
import 'firebase/firestore'
import 'firebase/auth'
import { Redirect } from 'react-router-dom'
import { signOut } from './../store/actions/authActions'
import { connect } from 'react-redux'
class Home extends Component {
render(){
console.log( "the props here in home.js", this.props)
return (
<div>
<h1> Welcome home</h1>
<button onClick={() => { firebase.auth().signOut();}}>Sign out!</button>
{/* <h1>Welcome {firebase.auth().currentUser.displayName}</h1> */}
{/* <img
alt="profile picture"
src={firebase.auth().currentUser.photoURL}
/> */}
</div>
);
}
}
const mapStateToProps = (state) => {
return{
auth: state.firebase.auth
}
}
const mapDispatchToProps = (dispatch) => {
return {
signOut: () => dispatch(signOut())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
so in chrome developer tools when I am trying to print the props in console
it gives auth as {isLoaded: false, isEmpty: true}

How do i resolve “Actions must be plain objects. Use custom middleware for async actions.”?

I am facing this issue in react native but it works in the react dom.
Also, I am not using middleware except react navigation middleware which is required by the react navigation to be integrated into redux.
What i am trying to do is to send phone and password to action which will fetch the token and save to redux store after which i also want to retrieve it for token authentication
Store
import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import AppReducer from './src/reducers';
import { AppNavigator, middleware } from './src/navigators';
const store = createStore(AppReducer, applyMiddleware(middleware));
class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppNavigator />
</Provider>
);
}
}
export default App;
Action
export const login = (phone, password) => {
return (dispatch, getState) => {
let headers = { 'Content-Type': 'application/json' };
let body = JSON.stringify({ phone, password });
return fetch('/api/auth/login/', { headers, body, method: 'POST' })
.then(res => {
if (res.status < 500) {
return res.json().then(data => {
return { status: res.status, data };
});
} else {
console.log('Server Error!');
throw res;
}
})
.then(res => {
if (res.status === 200) {
dispatch({ type: 'LOGIN_SUCCESSFUL', data: res.data });
return res.data;
} else if (res.status === 403 || res.status === 401) {
dispatch({ type: 'AUTHENTICATION_ERROR', data: res.data });
throw res.data;
} else {
dispatch({ type: 'LOGIN_FAILED', data: res.data });
throw res.data;
}
});
};
};
Reducer
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { RootNavigator } from '../navigators';
// Start with two routes: The Main screen, with the Login screen on top.
const firstAction = RootNavigator.router.getActionForPathAndParams('Main');
const tempNavState = RootNavigator.router.getStateForAction(firstAction);
const secondAction = RootNavigator.router.getActionForPathAndParams('Login');
const initialNavState = RootNavigator.router.getStateForAction(
secondAction,
tempNavState
);
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Login':
nextState = RootNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
case 'Logout':
nextState = RootNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Login' }),
state
);
break;
default:
nextState = RootNavigator.router.getStateForAction(action, state);
break;
}
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
const initialAuthState = {
token: null,
isAuthenticated: null,
isLoading: true,
user: null,
errors: {}
};
function auth(state = initialAuthState, action) {
switch (action.type) {
case 'USER_LOADING':
return { ...state, isLoading: true };
case 'USER_LOADED':
return {
...state,
isAuthenticated: true,
isLoading: false,
user: action.user
};
case 'LOGIN_SUCCESSFUL':
case 'REGISTRATION_SUCCESSFUL':
return {
...state,
token: action.token,
...action.data,
isAuthenticated: true,
isLoading: false,
errors: null
};
case 'AUTHENTICATION_ERROR':
case 'LOGIN_FAILED':
case 'REGISTRATION_FAILED':
case 'LOGOUT_SUCCESSFUL':
return {
...state,
errors: action.data,
token: null,
user: null,
isAuthenticated: false,
isLoading: false
};
case 'Login':
return { ...state, isLoggedIn: true };
case 'Logout':
return { ...state, isLoggedIn: false };
default:
return state;
}
}
const AppReducer = combineReducers({
nav,
auth
});
export default AppReducer;
LoginPage
import React, { Component } from 'react';
import { Button, StyleSheet, Text, View, TextInput } from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions/auth';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});
class LoginPage extends Component {
state = {
phone: '',
password: ''
};
render() {
console.log(this.state.phone);
return (
<View style={styles.container}>
<TextInput
style={{ height: 40, width: 100 }}
placeholder="Enter phone number"
onChangeText={text => this.setState({ phone: text })}
value={this.state.phone}
/>
<TextInput
style={{ height: 40, width: 100 }}
placeholder="Enter passoword"
onChangeText={text => this.setState({ password: text })}
value={this.state.password}
/>
{this.props.errors.length > 0 &&
this.props.errors.map(error => (
<Text key={error.field}>{error.message}</Text>
))}
<Button
onPress={() =>
this.props.login(this.state.phone, this.state.password)
}
title="Log in"
/>
</View>
);
}
}
LoginPage.navigationOptions = {
title: 'Log In'
};
const mapStateToProps = state => {
let errors = [];
if (state.auth.errors) {
errors = Object.keys(state.auth.errors).map(field => {
return { field, message: state.auth.errors[field] };
});
}
return {
errors,
isAuthenticated: state.auth.isAuthenticated
};
};
// const mapDispatchToProps = dispatch => {
// return {
// login: (phone, password) => {
// return dispatch(auth.login(phone, password));
// }
// };
// };
const mapDispatchToProps = {
login
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(LoginPage);
So I think the problem is that you do not use any redux middlewares that can allow you to write an action creator that return a function.
Right now, if you are not using redux middleware, you can only return an action like this
function increment() {
return {
type: INCREMENT_COUNTER
};
}
Try Redux-thunk library.
npm install --save redux-thunk
Now, in your applyMiddleware function, I think you can put that redux-thunk middleware and also your current middleware into it.
After you put it, I think it should not throw an error anymore.
Let me know how it goes.

React Component render not called first time with redux

I m trying to re-route the user to the url that the user was trying to navigate on the browser address bar by doing this:
I checked if a user is logged in by calling a service method. If the session in the cookie is expired or not valid, it will return a 401 status and I will redirect to the login screen
If user is logged in, allow.
If user is not logged in, route to the login screen and after login, route to the desired url.
The issue here is, when a user type a url like : http://url/app/order
it gets redirected to the login URL : http://url/auth/login
After the user enters his credentials, although the action gets dispatched, the render for the Authorizedroute component is not called. It gets called after I click the Login button again.
Following is my Login Component
export class LoginForm extends React.Component {
componentWillReceiveProps(newProps){
const { location, isAuthenticated, history } = newProps;
if(isAuthenticated){
if(location.state && location.state.referrer){
history.push(location.state.referrer.pathname);
}else{
history.replace('/app');
}
}
}
render() {
let usernameInput, passwordInput;
const { showErrorMessage, errorMessage, onLogin } = this.props;
return (
<div className='sme-login-center-view-container'>
<HeaderComponent />
<Col lg={4} lgOffset={4} sm={12} xs={12}>
<Col lg={10} lgOffset={2} sm={4} smOffset={4}>
<Form className="sme-login-box" onSubmit={(e)=> {
e.preventDefault();
let creds = {username: usernameInput.value, password: passwordInput.value};
onLogin(creds);
}
}>
<span className='text-center sme-login-title-text'><h4>User Login</h4></span>
<FormGroup >
{errorMessage ? (<Alert bsStyle="danger"><strong>Error!</strong> {errorMessage}</Alert>) : null }
</FormGroup>
<FormGroup controlId="formHorizontalUsername">
<FormControl type="username" placeholder="Username" bsStyle="form-rounded"
inputRef={(ref) => {usernameInput = ref}}
/>
<FormControl.Feedback>
<span className="fa fa-user-o sme-login-input-feedback-span"></span>
</FormControl.Feedback>
</FormGroup>
<FormGroup controlId="formHorizontalPassword">
<FormControl type="password" placeholder="Password"
inputRef={(ref) => {passwordInput = ref}}/>
<FormControl.Feedback>
<span className="fa fa-lock sme-login-input-feedback-span"></span>
</FormControl.Feedback>
</FormGroup>
<FormGroup>
<Button type="submit" block >Login</Button>
</FormGroup>
</Form>
</Col>
</Col>
</div>
);
}
}
LoginContainer
const mapStateToProps = state => {
return state.authenticationReducer.login
}
export const Login = withRouter(connect( mapStateToProps,{ onLogin: loginUser })(LoginForm))
Login Action
export function requestLogin(creds) {
return {
type: LOGIN_REQUEST,
isFetching: true,
isAuthenticated: false,
creds
}
}
export function receiveLogin() {
return {
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true
}
}
export function loginError(message) {
return {
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
errorMessage: message
}
}
export function loginUser(creds) {
return dispatch => {
dispatch(requestLogin(creds))
return axios.post(url, data)
.then(response => {
if (!response.status === 200) {
dispatch(loginError(response.statusText))
} else {
dispatch(receiveLogin())
}
}
).catch(function (error) {
dispatch(loginError(error.response.statusText))
}
) }
}
Login Reducer:
export function login(state = {
isFetching: false,
isAuthenticated: false
}, action) {
switch (action.type) {
case LOGIN_REQUEST:
case LOGIN_SUCCESS:
case LOGIN_FAILURE:
return Object.assign({}, state, action)
default:
return state
}
}
Authorized Route Component
class AuthorizedRouteComponent extends React.Component {
componentWillMount() {
this.props.getUser();
}
render() {
const { component: Component, pending, logged, location, ...rest } = this.props;
return (
<Route {...rest} render={props => {
if (pending) return <div>Loading...</div>
return logged
? <Component {...this.props} />
:<Redirect to={{
pathname: '/auth/login',
state: { referrer: location }
}}/>
}} />
)
}
}
const mapStateToProps = state => {
return state.authenticationReducer.loggedUser
}
const AuthorizedRoute = connect(mapStateToProps, { getUser: getLoggedUser })(AuthorizedRouteComponent);
export default AuthorizedRoute
Find Logged user action
export function requestFetch() {
return {
type: FETCH_REQUEST,
pending: true,
logged: false
}
}
export function receiveFetch(userData) {
return {
type: FETCH_SUCCESS,
pending: false,
logged: true,
userData
}
}
export function fetchError(message) {
return {
type: FETCH_FAILURE,
pending: false,
logged: false,
errorMessage:message
}
}
export function getLoggedUser() {
return dispatch => {
dispatch(requestFetch())
return axios.get('/o3/rest/user/userdetails')
.then(response => {
if (!response.status === 200) {
dispatch(fetchError(response.statusText))
} else {
dispatch(receiveFetch(response.data))
}
}
).catch(function (error) {
dispatch(fetchError(error.response.statusText))
}
)
}
}
And finally my Logged user reducer
export function loggedUser(state = initialState, action) {
switch (action.type) {
case FETCH_REQUEST:
case FETCH_SUCCESS:
case FETCH_FAILURE:
let obj = Object.assign({}, state, action);
return obj;
default:
return state
}
}
You can use localStorage to save the user in your action creator that is a charge of fetching the user:
export function getLoggedUser() {
return dispatch => {
dispatch(requestFetch())
return axios.get('/o3/rest/user/userdetails')
.then(response => {
if (!response.status === 200) {
dispatch(fetchError(response.statusText))
} else {
localStorage.setItem('userData',response.data)
}
}
).catch(function (error) {
dispatch(fetchError(error.response.statusText))
}
)
}
}
And then in the in the index file ask:
const userData = localStorage.getItem('userData')
if(userData){
store.dispatch({
type: FETCH_SUCCESS,
pending: false,
logged: true,
userData
})
}
Each time you refresh the page or just type a URL like http://url/app/order is going to verify is there exist a user currently logged, if exist will dispatch an action an update your state.

Resources