Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difficulties integrating UserAuthWrapper into react-boilerplate #40

Closed
katcu opened this issue May 24, 2016 · 14 comments
Closed

Difficulties integrating UserAuthWrapper into react-boilerplate #40

katcu opened this issue May 24, 2016 · 14 comments
Labels

Comments

@katcu
Copy link

katcu commented May 24, 2016

Im doing the job integrates redux-auth-wrapper with react-boilerplate,(referred blog) it did not redirect to '/login' by default even set [predicate(authData): Bool] (Function) to false. Please help!
@mjrussell @johnwiseheart @cab @gaearon @DveMac @mxstbr

const UserIsAuthenticated = UserAuthWrapper({
  //authSelector: state => null,
  authSelector: state => state.login.userState,
  predicate: authData=>false,
  redirectAction: routerActions.replace,
  wrapperDisplayName: 'UserIsAuthenticated'
})
export default function createRoutes(store) {
  // Create reusable async injectors using getHooks factory
  const { injectReducer, injectSagas } = getHooks(store);

  return [
     {
      path: '/home',
      name: 'home',
      getComponent(nextState, cb) {
        const importModules = Promise.all([
          System.import('containers/Home/reducer'),
          System.import('containers/Home'),
        ]);

        const renderRoute = loadModule(cb);

        importModules.then(([homeReducer, component]) => {
          injectReducer('Home', homeReducer.default);
          //remain on the route:'/home'
          renderRoute(UserIsAuthenticated(component));
        });

        importModules.catch(errorLoading);
      },
    },  
@mjrussell
Copy link
Owner

@katcu do you have a branch you are trying this on that I could look at? I haven't used that boilerplate but it should integrate cleanly

@katcu
Copy link
Author

katcu commented May 24, 2016

hold on pls... @mjrussell

@katcu
Copy link
Author

katcu commented May 24, 2016

@mjrussell
I simply did the follow steps, then visit http://localhost:3000/features . it should redirect me to /login, right?

Clone this repo using$ git clone --depth=1 https://github.com/mxstbr/react-boilerplate.git
Run $ npm run setupto install dependencies and clean the git repo.
At this point you can run $ npm startto see the example app at http://localhost:3000.

npm install --save redux-auth-wrapper

change the /app/routes.js code as following:

// These are the pages you can go to.
// They are all wrapped in the App component, which should contain the navbar etc
// See http://blog.mxstbr.com/2016/01/react-apps-with-pages for more information
// about the code splitting business
import { getHooks } from './utils/hooks';
import { routerActions } from 'react-router-redux'
import { UserAuthWrapper } from 'redux-auth-wrapper'

const errorLoading = (err) => {
  console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
};

const loadModule = (cb) => (componentModule) => {
  cb(null, componentModule.default);
};

const UserIsAuthenticated = UserAuthWrapper({
  authSelector: state => null,
  //authSelector: null,
  predicate: authData=>false,
  redirectAction: routerActions.replace,
  wrapperDisplayName: 'UserIsAuthenticated'
})

export default function createRoutes(store) {
  // create reusable async injectors using getHooks factory
  const { injectReducer, injectSagas } = getHooks(store);

  return [
    {
      path: '/',
      name: 'home',
      getComponent(nextState, cb) {
        const importModules = Promise.all([
          System.import('containers/HomePage/reducer'),
          System.import('containers/HomePage/sagas'),
          System.import('containers/HomePage'),
        ]);

        const renderRoute = loadModule(cb);

        importModules.then(([reducer, sagas, component]) => {
          injectReducer('home', reducer.default);
          injectSagas(sagas.default);

          renderRoute(UserIsAuthenticated(component));
        });

        importModules.catch(errorLoading);
      },
    }, {
      path: '/features',
      name: 'features',
      getComponent(nextState, cb) {
        System.import('containers/FeaturePage')
          .then(loadModule(cb))
          .catch(errorLoading);
      },
    }, {
      path: '*',
      name: 'notfound',
      getComponent(nextState, cb) {
        System.import('containers/NotFoundPage')
          .then(loadModule(cb))
          .catch(errorLoading);
      },
    },
  ];
}

@katcu
Copy link
Author

katcu commented May 25, 2016

@mjrussell
Copy link
Owner

@katcu thanks Im going to look into this today

@mjrussell
Copy link
Owner

mjrussell commented May 25, 2016

@katcu
The issue is in how the code split route loading is set up with the boilerplate. I haven't used System.import much but you need to move the application of the wrapper to the loadModule function instead of inside the renderRoute.

If you want to apply the wrapper HOC in the routes file you can do the following:

edit: There can be some unnecessary mount/unmounting when applying HOCs inside getComponent because each time the route is rendered, a new component is returned from the HOC. See #44 for details. This can result in poor performance/subtle bugs. Instead scroll down to see how to apply the HOC inside the Component file

// These are the pages you can go to.
// They are all wrapped in the App component, which should contain the navbar etc
// See http://blog.mxstbr.com/2016/01/react-apps-with-pages for more information
// about the code splitting business
import { getHooks } from './utils/hooks';
import { routerActions } from 'react-router-redux'
import { UserAuthWrapper } from 'redux-auth-wrapper'

const errorLoading = (err) => {
  console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
};

const loadModule = (cb, hoc) => (componentModule) => {
  if (hoc) {
    cb(null, hoc(componentModule.default));
  } else {
    cb(null, componentModule.default);
  }
};

const UserIsAuthenticated = UserAuthWrapper({
  authSelector: state => null,
  predicate: authData=>false,
  redirectAction: routerActions.replace,
  wrapperDisplayName: 'UserIsAuthenticated'
});


export default function createRoutes(store) {
  // create reusable async injectors using getHooks factory
  const { injectReducer, injectSagas } = getHooks(store);

  return [
    {
      path: '/',
      name: 'home',
      getComponent(nextState, cb) {
        const importModules = Promise.all([
          System.import('containers/HomePage/reducer'),
          System.import('containers/HomePage/sagas'),
          System.import('containers/HomePage'),
        ]);

        const renderRoute = loadModule(cb, UserIsAuthenticated);

        importModules.then(([reducer, sagas, component]) => {
          injectReducer('home', reducer.default);
          injectSagas(sagas.default);
          renderRoute(component);
        });

        importModules.catch(errorLoading);
      },
    }, {
      path: '/features',
      name: 'features',
      getComponent(nextState, cb) {
        System.import('containers/FeaturePage')
          .then(loadModule(cb))
          .catch(errorLoading);
      },
    }, {
      path: '*',
      name: 'notfound',
      getComponent(nextState, cb) {
        System.import('containers/NotFoundPage')
          .then(loadModule(cb))
          .catch(errorLoading);
      },
    },
  ];
}

Alternatively, you could move the user wrapper into the component definition

Using decorators: (assuming you add the transform-decorators-legacy to your babel plugins)

@UserIsAuthenticated
export class HomePage extends React.Component {
...

Or just regular HOC application:

...
// Wrap the component to inject dispatch and state into it
export default UserIsAuthenticated(connect(createSelector(
  selectRepos(),
  selectUsername(),
  selectLoading(),
  selectError(),
  (repos, username, loading, error) => ({ repos, username, loading, error })
), mapDispatchToProps)(HomePage));

@mjrussell mjrussell changed the title UserAuthWrapper not work with asynchronously loaded component Difficulties integrating UserAuthWrapper into react-boilerplate May 25, 2016
@katcu
Copy link
Author

katcu commented May 25, 2016

@mjrussell Thank you for looking into this. The way that use System.import to import components led to the issue. and as you said, it's difficult integrating it with other NICE modules. I've just changed to a similar boilerplate, issues resolved and everything works as expected.
Thanks again for the nice clean wrapper 👍

@chaintng
Copy link

@mjrussell your approach work like magic!
Thank you so much!

@hurkanyakay
Copy link

hurkanyakay commented Oct 17, 2016

Recently solved this problem thanks to this issue. I noticed this important warning in main page that:

If you are using getComponent in React Router you should not apply the auth-wrapper inside getComponent. This will cause React Router to create a new component each time the route changes.

I guess we can't use
const loadModule = (cb, hoc) => (componentModule) => {
approach.

@mjrussell
Copy link
Owner

@hurkanyakay Yeah good point. We realized afterwards that the loadModule approach can have some weird edge cases. See #44 for more details. That issue prompted the warning in the README but I never came back to this issue. Im going to edit my original suggestion to encourage using the HOC in the component file.

@JustinJKlein
Copy link

Hi all,
I am still a bit lost on the best way to go about getting this setup to work with my project built with the react-boilerplate. I want/need to run this as a hoc but it is not apparent to me what I need to get it other than my export for the component I want to wrap needs to be altered in the manner provided mjrussel above. But, I do not know what imports are needed or where to define my const for UserIsAuthenticated because they are not in the example for hoc. I know I am likely missing something simple so I apologize in advance if its a no brainer.

@mjrussell
Copy link
Owner

@JustinJKlein it is up to you to decide where you want to define your HOC wrappers. I see many examples (and my own projects) create a file authWrappers.js in which you define the HOC and then they import it either into their Route definition file or their Component definition file. Because you are using react-boilterplate with System.import, you need to apply them inside the Component definition file.

So it might look something like:
authWrappers.js

import { UserAuthWrapper } from 'redux-auth-wrapper'

// Redirects to /login by default
export const UserIsAuthenticated = UserAuthWrapper({
  authSelector: state => state.user, // how to get the user state
  redirectAction: routerActions.replace, // the redux action to dispatch for redirect
  wrapperDisplayName: 'UserIsAuthenticated' // a nice name for this auth check
});

MyComponent.js

import React, { Component } from 'react';

import { UserIsAuthenticated } from './authWrappers';

class MyComponent extends Component {
  render() {
    return <h1>Renders a thing</h1>;
  }
}

export default UserIsAuthenticate(MyComponent);

@LearningDave
Copy link

LearningDave commented Mar 14, 2017

Hi there,

im currently trying to add auth to my navigation bar (based react-boilerplate). After some help from @mjrussell yesterday, i could make it work for containers generelly (thanks mate :) )

However now i want to use this approach (readme code example):

// Admin Authorization, redirects non-admins to /app and don't send a redirect param
const UserIsAdmin = UserAuthWrapper({
  authSelector: state => state.user,
  redirectAction: routerActions.replace,
  failureRedirectPath: '/app',
  wrapperDisplayName: 'UserIsAdmin',
  predicate: user => user.isAdmin,
  allowRedirectBack: false
})

// Now to secure the component: first check if the user is authenticated, and then check if the user is an admin
<Route path="foo" component={UserIsAuthenticated(UserIsAdmin(Admin))}/>

My Header(navigation) container looks like this:

class Header extends React.Component { // eslint-disable-line react/prefer-stateless-function
  render() {
      return (
        <div>
          <NavBar>
            <HeaderLink to="/front">
              <FormattedMessage {...messages.front} />
            </HeaderLink>
            <HeaderLink to="/admin">
              <FormattedMessage {...messages.admin} />
            </HeaderLink>
            <Button onClick={this.props.onClickLogout}>
              <FormattedMessage {...messages.logout} />
            </Button>
          </NavBar>
        </div>
      );
  }
}

I would like to enable/disable HeaderLinks Component using a authWrapper.
Could you please help me out to find a fitting solution?

Thanks in advance! :)

@mjrussell
Copy link
Owner

@LearningDave please see the section in the Readme on Hiding and Alternate Components which is I think what you are asking. You dont want the redirection in this case

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants