I'm trying to create a session handler, for all the components where i need to determine if a user is logged in. however if i add a console.log(authUser) in withAuthentication it returns a user, but however not in the SideBar. Here it always be null. What am i doing wrong?
withAuthentication
const withAuthentication = (Component) => {
class WithAuthentication extends React.Component {
componentDidMount() {
const { onSetAuthUser } = this.props;
firebase.auth.onAuthStateChanged(authUser => {
authUser
? onSetAuthUser(authUser)
: onSetAuthUser(null);
});
}
render() {
return (
<Component />
);
}
}
const mapDispatchToProps = (dispatch) => ({
onSetAuthUser: (authUser) => dispatch({ type: 'AUTH_USER_SET', authUser }),
});
return connect(null, mapDispatchToProps)(WithAuthentication);
}
export default withAuthentication;
App
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="app">
<SideBar />
<div>
<Navigation/>
<BrowserRouter>
<Switch>
<Route exact path={routes.LANDING} component={() => <LandingPage />} />
<Route render={() => <h3>No Match</h3>} />
</Switch>
</BrowserRouter>
</div>
</div>
)
}
}
export default withAuthentication(App);
SideBar
const INITIAL_STATE = {
sideBarOpen: false
};
class SideBar extends Component {
constructor(props) {
super(props.authUser);
console.log(props)
this.state = { ...INITIAL_STATE };
}
render() {
const {
sideBarOpen,
authUser
} = this.state;
const {
children,
} = this.props;
return (
{authUSer ?
'authed' :
'not authed'
}
)
}
}
const mapStateToProps = (state) => ({
authUser: state.sessionState.authUser
});
export default connect(mapStateToProps)(SideBar);
reducer
const INITIAL_STATE = {
authUser: null,
};
const applySetAuthUser = (state, action) => ({
...state,
authUser: action.authUser
});
function sessionReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case 'AUTH_USER_SET' : {
return applySetAuthUser(state, action);
}
default : return state;
}
}
const rootReducer = combineReducers({
sessionState: sessionReducer
});
export default rootReducer;
As your mapStateToProps setup
const mapStateToProps = (state) => ({
authUser: state.sessionState.authUser
});
You need to get authUser from component props not from component state.
So your code should be const authUser = this.props.authUser.
Or your way const {authUser} = this.props
Related
I created an HOC to deal with loading. It uses a property isLoading to decide what to show.
When I open a page directly from the URL I haven't any issue and everything works fine (because isLoading property is setted up on true by default).
The problem is that when I click on a link (<Link ... />) things doesn't work as expected because isLoading is now false because the page where I am navigating from setted the state to this value.
So, this is the HOC:
import React from 'react';
export default function WithLoading(WrappedComponent, loadingDelegate) {
class LoadingComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true
};
this.loadingDelegate = loadingDelegate;
}
componentDidMount() {
loadingDelegate(this);
}
render() {
if (this.props.isLoading === true) {
return (
<div>Loading...</div>
);
} else {
return <WrappedComponent {...this.props} />;
}
}
}
return LoadingComponent;
}
And below the component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from './SurveysStore';
import WithLoading from '../LoadingHOC';
//edit/:id
class SurveyDetailRoutedComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.survey.questions.length}
</div>
);
}
}
const SurveyDetailRoutedComponentWithLoading = WithLoading(SurveyDetailRoutedComponent, (_context) => _context.props.requestSurvey(parseInt(_context.props.match.params.id)));
export default connect(
state => state.surveysReducer,
dispatch => bindActionCreators(actionCreators, dispatch)
)(SurveyDetailRoutedComponentWithLoading);
The error I have is that survey is null, because I tried to render a not already resolver property.
The problem occurs only when I render this component via a <Link> contained in a page rendered in the same way.
I tried to set isLoading=true on the routing, but this doesn't work for me:
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Layout>
<Route exact path='/' component={(props) => <Home {...props} />} />
<Route path='/survey/edit/:id' component={(props) => <SurveyDetailRoutedComponent {...props} isLoading={true} />} />
</Layout>
);
}
}
The store itself is very simple and I imagine my error is on how I deal with redux, because it looks like the routing operation is not resetting the loading.
const resetLoading = "RESET_LOADING";
const requestSurveys = "REQUEST_SURVEYS";
const receiveSurveys = "RECEIVE_SURVEYS";
const requestSurvey = "REQUEST_SURVEY";
const receiveSurvey = "RECEIVE_SURVEY";
const initialState = { surveys: [], isLoading: true, survey: null };
export const actionCreators = {
resetLoading: () => function (dispatch, getState) {
dispatch({ type: resetLoading })
},
requestSurveys: () => async function (dispatch, getState) {
dispatch({ type: requestSurveys });
const response = await fetch(...)
const responseAsJson = await response.json();
dispatch({ type: receiveSurveys, surveys: responseAsJson.data.surveys });
},
requestSurvey: id => async function (dispatch, getState) {
dispatch({ type: requestSurvey });
const response = await fetch(...)
const responseAsJson = await response.json();
dispatch({ type: receiveSurvey, survey: responseAsJson.data.survey });
}
};
export const reducer = function(state, action) {
state = state || initialState;
switch (action.type) {
case resetLoading:
return {
...state,
isLoading: true
};
case requestSurveys:
return {
...state,
isLoading: true
};
case requestSurvey:
return {
...state,
isLoading: true
};
case receiveSurveys:
return {
...state,
isLoading: false,
surveys: action.surveys
};
case receiveSurvey:
return {
...state,
isLoading: false,
survey: action.survey
};
default:
return state;
}
}
I'm learning Redux state management and got stuck with an issue. The mapStateToProps within a component is not triggered when the state changes. Gone through a lot of blogs, couldn't able to figure out the problem.
The store seems to update properly, as the "subscribe" method on store prints new changes. Attached screenshot as well.
Actions.js
export const GET_ITEMS_SUCCESS = "GET_ITEMS_SUCCESS";
export const GET_ITEMS_FAILURE = "GET_ITEMS_FAILURE";
export const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS, payload: items
});
export const getItemsFailure = (error) => ({
type: GET_ITEMS_FAILURE, error: error
});
export function getItems(dispatch) {
return dispatch => {
fetch(myList)
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
store.dispatch(getItemsSuccess(res));
return res;
})
.catch(error => {
store.dispatch(getItemsFailure(error));
})
}
}
Reducer
let initialState = {items: [], error: null}
function GetItemsReducer (state = initialState, action) {
switch (action.type) {
case GET_ITEMS_SUCCESS:
return Object.assign({}, state, {pending: false, items: action.payload});
case GET_ITEMS_FAILURE:
return Object.assign({}, state, {pending: false, error: action.error});
default:
return state;
}
}
export default const rootReducer = combineReducers({
GetItemsReducer: GetItemsReducer
});
Store
const mystore = createStore(rootReducer, applyMiddleware(thunk, promise));
mystore.subscribe(() => console.log("State Changed;", mystore.getState()));
Component
class Home extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchItems();
}
render() {
return (
<div>{this.props.items.length}</div>
)
}
}
const mapStateToProps = (state) => {
console.log('mapStateToProps ----------> ', state);
return {
items: state.GetItemsReducer.items,
error: state.GetItemsReducer.error
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItems: bindActionCreators(getItems, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Main
class App extends React.Component {
render() {
return (
<Provider store={mystore}>
<Home />
</Provider>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
Thanks in advance.
This is my initial state in authReducer.
const initialState = {
token:'',
isLogin: false,
error: {}
};
function AuthReducer(state = initialState, action) {
switch(action.type){
case AUTH_SUCCESS:
return sessionStorage.setItem('token',action.payload),{
...state,
isLogin: true,
token: action.payload,
error: {}
};
case AUTH_FAILED:
return {
...state,
isLogin: false,
token: '',
error: action.payload
};
default:
return state
}
}
export default AuthReducer;
This is my login component that works fine. User can authenticate and everything is working fine:
class Login extends Component {
constructor(props){
super(props);
this.state ={
username: '',
password: ''
}
}
onSubmit = e => {
e.preventDefault();
this.props.login(this.state.username, this.state.password);
}
render() {
.....
)
}
}
const mapStateToProps = state => {
return {
get: { auth : state.AuthReducer}
}
}
const mapDispatchToProps = dispatch => {
return {
login: (username, password) => {
return dispatch(login(username, password));
}
};
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login));
And my router is something like this:
class App extends Component {
constructor(props){
super(props)
}
render() {
let PrivateRoute = ({ component: ChildComponent, isLogin, ...rest}) => {
console.log({...rest}, "this is rest")
return <Route
{...rest}
render={props => {
if (!isLogin) {
return <Redirect to="/login" />;
} else {
return <ChildComponent {...props} />
}
}} />
}
return (
<div>
<Home />
<Switch>
<PrivateRoute path="/" exact isLogin={this.props.auth.isLogin} component={mainContent} />
<PrivateRoute path="/posts" isLogin={this.props.auth.isLogin} component={Posts} />
<Route path="/login" component={Login} />
</Switch>
</div>
);
}
}
const mapStateToProps = state => {
return {
auth: state.AuthReducer
}
}
const mapDispatchToProps = dispatch => {
return {
getUser: () => {
return dispatch(getUser());
}
};
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
In react-router, they said you must use withRouter from react-router-dom. my problem is login works fine. but I don't have access to my props in my privateroute so when react renders a new page my isLogin sets to false. I have some idea that router doesn't know about my redux or protected components cant are not aware of changes in props.all of my child components in my PrivateRoute are connected with react-redux.
Here the issue is you are not getting isLogin value from reducer. When you try to get reducer value in your router file:
const mapStateToProps = state => {
return {
auth: state.AuthReducer
}
}
You will get this.props.auth.AuthReducer as undefined as the state value in mapStateToProps will be your initial value from your reducer. So, either you can get Login from state directly like below and try to access this.props.auth:
const mapStateToProps = state => {
return {
auth: state.isLogin
};
};
or you can replace you code with this and use this.props.auth.isLogin:
const mapStateToProps = state => {
return {
auth: state
};
};
Refer this for more:
https://codesandbox.io/s/react-router-dom-with-private-route-sr61v
I have a small issue that I can't figure out why it happens. After signing in, if the user attempts to access the login page, it will redirect to the home page. However, I'm not sure why my /signin page flashes/appears for half a second before the redirect happens.
I'll try my best to explain the flow of the code:
Whenever a user logins, state.signIn.signInSuccess is set to true. This state is mapped to a prop in my routes component, where I pass it down as a prop to my AuthorizedRoutes component. The authorizedRoutes component check if signInSuccess is true or not and will render the /signin page or redirect. When I try to access the signin page I console.log the state, signInSuccess shows up as false for half a second before changing to true even though the user is already signed in. It's a small issue, but I want to understand why the state is changing to false for that half a second.
If anyone could help me, I'd appreciate it. Thanks
////signin action creators////
import { firebaseApp } from '../firebase.js';
import { SIGNIN_PENDING, SIGNIN_SUCCESS, SIGNIN_FAIL } from '../constants/signin.js';
const setSignInPending = signInPending => {
return{
type:SIGNIN_PENDING,
signInPending
};
};
const setSignInSuccess = signInSuccess => {
return{
type:SIGNIN_SUCCESS,
signInSuccess
};
};
const setSignInFail = signInError => {
return{
type:SIGNIN_FAIL,
signInError
};
};
export const signIn = (email, password) => {
return dispatch => {
dispatch(setSignInPending(true));
return firebaseApp.auth().signInWithEmailAndPassword(email, password)
.then(res => {
dispatch(setSignInPending(false));
dispatch(setSignInSuccess(true));
})
.catch(error => {
console.log('error', error);
dispatch(setSignInPending(false));
dispatch(setSignInFail(error.code));
})
}
};
export const signedIn = () => {
return dispatch => {
dispatch(setSignInSuccess(true));
dispatch(setSignInFail(null));
};
};
////signin reducer////
import { SIGNIN_PENDING, SIGNIN_SUCCESS, SIGNIN_FAIL } from '../constants/signin.js';
const defaultState = {
signInPending: false,
signInSuccess: false,
signInError: null,
};
const signIn = (state = defaultState, action) => {
let signInState = null;
switch (action.type){
case SIGNIN_PENDING:
signInState = {
...state,
signInPending: action.signInPending
};
return signInState;
case SIGNIN_SUCCESS:
signInState = {
...state,
signInSuccess: action.signInSuccess,
};
return signInState;
case SIGNIN_FAIL:
signInState = {
...state,
signInError: action.signInError
};
return signInState;
default:
return state;
};
};
export default signIn;
////Routes component////
class Routes extends React.Component{
constructor(props){
super(props);
}
componentWillMount(){
firebaseApp.auth().onAuthStateChanged(user => {
// user ? dispatch(setSignedInTrue()) : dispatch(setSignedInFalse());
if (user){
this.props.signedIn();
}
});
}
render(){
return(
<Router>
<Switch>
<Route exact path='/' render={(props) => (<App signInSuccess={this.props.signInSuccess} {...props}/>)} />
<AuthorizedRoute signedIn={this.props.signInSuccess} path="/signup" component={SignUp} />
<AuthorizedRoute signedIn={this.props.signInSuccess} path="/signin" component={SignIn} />
<Route component={InvalidPage} />
</Switch>
</Router>
);
}
};
Routes.propTypes = {
signedIn: PropTypes.func.isRequired
};
const mapStateToProps = (state) => {
return{
signInSuccess: state.signIn.signInSuccess
};
};
const mapDispatchToProps = (dispatch) => {
return{
signedIn: () => dispatch(signedIn()),
signOut: () => dispatch(signOut())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Routes);
////AuthorizedRoutes Component////
class AuthorizedRoute extends React.Component{
constructor(props){
super(props);
};
render(){
console.log(this.props);
const { component: Component, ...rest } = this.props;
return(
<Route {...rest} render={props => {
if (this.props.signedIn){
return <Redirect to="/" />
} else if (this.props.signedIn === false){
return <Component {...props} />
} else{
return null;
}
}} />
);
}
};
AuthorizedRoute.propTypes = {
signInSuccess: PropTypes.bool
};
export default AuthorizedRoute;
You trigger 2 separate Redux state changes:
dispatch(setSignInPending(false));
dispatch(setSignInSuccess(true));
those will each cause a re-render, so when you setSignInPending(false), there is a render where signInPending is false but setSignInSuccess(true) hasn't been called.
This is a common usage of Redux, to use it with simple getters/setters. Think of it more like events which occur which can mutate multiple aspects of the state. I think you actually only have 1 event: signInSuccess (or fail), and you simply need to set pending to false in that reducer case because you know you got a response. That way there is only 1 Redux state change and when the render is called the state isn't in an invalid state as it is now.
////signin reducer////
import { SIGNIN_SUCCESS, SIGNIN_FAIL } from '../constants/signin.js';
const defaultState = {
signInPending: false,
signInSuccess: false,
signInError: null,
};
const signIn = (state = defaultState, action) => {
let signInState = null;
switch (action.type){
case SIGNIN_SUCCESS:
signInState = {
...state,
signInSuccess: action.signInSuccess,
signInPending: false,
signInError: null
};
return signInState;
case SIGNIN_FAIL:
signInState = {
...state,
signInError: action.signInError,
signInPending: false,
signInSuccess: false
};
return signInState;
default:
return state;
};
};
export default signIn;
I'm working on a personal project and I have trouble with checking if a user is logged in or not so that when they try to go to /login, it'll redirect them to the home page. Currently what my code does is the following:
Defined my own Route components
When checkIfSignedIn is called, it dispatches actions that set the isLoggedIn state to either True or False.
The AuthenticatedRouter component checks if the store's isLoggedIn state is true or false and will either render the component or redirect to home.
This works, but the issue is that isLoggedIn is initially false, so if they are logged in, the login form shows up for a second and then redirects after isLoggedIn becomes true.
I can't figure out how to fix the problem with rendering the component with the default state and then suddenly redirects because the state changes. Thanks
I'm using firebase for the user account.
Routes.js
const Routes = (props) => {
props.checkIfSignedIn();
return(
<Router>
<Switch>
<Route exact path='/' component={App}/>
<AuthorizedRoute path="/signup" component={SignUp}/>
<AuthorizedRoute path="/signin" component={SignIn}/>
<Route component={InvalidPage}/>
</Switch>
</Router>
);
};
const mapDispatchToProps = (dispatch) => {
return{
checkIfSignedIn: () => dispatch(checkIfSignedIn())
};
};
export default connect(null, mapDispatchToProps)(Routes);
AuthorizedRoute.js
class AuthorizedRoute extends React.Component{
constructor(props){
super(props);
this.state = {
isLoggedIn: false
};
};
render(){
const { component: Component, ...rest } = this.props;
return(
<Route {...rest} render={props => {
if (this.props.isLoggedIn){
console.log(this.state.isLoggedIn);
return <Redirect to="/" />
} else{
return <Component {...props} />
}
}} />
);
}
};
const mapStateToProps = (state) => {
return{
isLoggedIn : state.authentication.isLoggedIn
};
};
export default connect(mapStateToProps, null)(AuthorizedRoute);
authentication.js - action creators
const setSignedInFalse = () => {
return{
type: IS_SIGNED_IN_FALSE
};
};
const setSignedInTrue = () => {
return{
type: IS_SIGNED_IN_TRUE
};
};
const checkIfSignedIn = () => {
return dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user){
dispatch(setSignedInTrue());
} else{
dispatch(setSignedInFalse());
}
});
};
};
export default checkIfSignedIn;
authentication.js - reducer
const defaultState = {
isLoggedIn: false
};
const authentication = (state = defaultState, action) => {
let authenticationState = null;
switch (action.type){
case IS_SIGNED_IN_FALSE:
authenticationState = {
isLoggedIn: false
};
return authenticationState;
case IS_SIGNED_IN_TRUE:
authenticationState = {
isLoggedIn: true
};
return authenticationState;
default:
return state;
};
};
export default authentication;
Hacky solution (Not sure if this is frowned upon).
I set my Auth reducer's default state, isLoggedIn : null instead of false. Then in my AuthorizedRoute component I now have 3 conditions to render null first and then either the component or redirect.
//AuthorizedRoute Component
if (this.props.isLoggedIn){
return <Redirect to="/" />
} else if (this.props.isLoggedIn === false){
return <Component {...props} />
} else{
return null;
}
What you may do is something like this: (in your auth reducer).
let user = JSON.parse(localStorage.getItem('user'));
const initialState = user ? { isLoggedIn: true } : {};
Also you had to set the localstorage when your user is logging in and remove it when he is logging out.
class AuthorizedRoute extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false
};
};
render() {
const { component: Component, ...rest } = this.props;
const user = JSON.parse(localStorage.getItem('user'));
return (
<Route { ...rest } render={ props => user ? <Component {...props} /> : <App /> } />
);
}
};
const mapStateToProps = (state) => {
return {
isLoggedIn : state.authentication.isLoggedIn
};
};
export default connect(mapStateToProps, null)(AuthorizedRoute);