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

Using generateRequireSignInWrapper throws error #53

Open
Rinbo opened this issue Dec 19, 2018 · 7 comments
Open

Using generateRequireSignInWrapper throws error #53

Rinbo opened this issue Dec 19, 2018 · 7 comments

Comments

@Rinbo
Copy link

Rinbo commented Dec 19, 2018

Using the generateRequireSignInWrapper gives me this error message:

Could not find "store" in either the context or props of "Connect(GatedPage)". Either wrap the root component in a Provider, or explicitly pass "store" as a prop to "Connect(GatedPage)".

Here is my code:

index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";
import reduxThunk from "redux-thunk";
import { verifyCredentials } from "./apis/redux-token-auth-config";

import reducers from "./reducers";
import App from "./components/App";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  reducers,
  composeEnhancers(applyMiddleware(reduxThunk))
);
verifyCredentials(store);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

app.js

import React from "react";
import "semantic-ui-css/semantic.min.css";
import { Router, Route, Switch } from "react-router-dom";
import history from "../history";
import { generateRequireSignInWrapper } from "redux-token-auth";

import LandingPage from "./LandingPage";
import CardCreate from "./CardsCreate";
import CardShow from "./CardShow";
import SignInPage from "./SignInPage";

const requireSignIn = generateRequireSignInWrapper({
  redirectPathIfNotSignedIn: "/signin"
});

const Routes = () => (
  <div className="ui container">
    <Router history={history}>
      <div>
        <Switch>
          <Route path="/" exact component={requireSignIn(LandingPage)} />
          <Route path="/cards/new" exact component={CardCreate} />
          <Route path="/cards/:id" exact component={CardShow} />
          <Route path="/signin" exact component={SignInPage} />
        </Switch>
      </div>
    </Router>
  </div>
);

const App = () => {
  return Routes();
};

export default App;
@suyesh
Copy link

suyesh commented Dec 26, 2018

@Rinbo Did you get this working?

@Rinbo
Copy link
Author

Rinbo commented Dec 26, 2018

@suyesh nope.

@suyesh
Copy link

suyesh commented Dec 26, 2018

@rinboi tried everything and nothing worked. Going to good old create from scratch mode.

@Rinbo
Copy link
Author

Rinbo commented Dec 26, 2018

@suyesh Yeah, same here.

@tochman
Copy link

tochman commented Feb 24, 2019

@Rinbo I would need your advice on how to solve this issue.

@Rinbo
Copy link
Author

Rinbo commented Feb 24, 2019

@tochman I threw out this module and implemented my own middleware instead:

import axios from "axios";

let HEADERS = ["access-token", "token-type", "client", "expiry", "uid"];

const tokenMiddleware = () => store => next => action => {
  if (!action) {
    action = { type: "" };
  }
  
  let customHeaders = []
  let validateAction = "VALIDATE_TOKEN"
  let logoutAction = "SIGN_OUT"
    
  HEADERS = [...new Set([...HEADERS, ...customHeaders])];
  if (action.type === validateAction) {
    HEADERS.forEach(
      token =>
        (axios.defaults.headers.common[token] = localStorage.getItem(token))
    );
  } else if (action.type === logoutAction) {
    HEADERS.forEach(token => localStorage.removeItem(token));
  } else {
    let { headers } = action;
    if (headers) {
      if (headers["access-token"]) {
        HEADERS.forEach(token => {
          axios.defaults.headers.common[token] = headers[token];
          localStorage.setItem(token, headers[token]);
        });
      }
    }
  }
  return next(action);
};

export default tokenMiddleware;

Then I applied it to store in root index.js:

...
import reduxThunk from "redux-thunk";
import tokenMiddelware from "./tokenMiddleware";

import reducers from "./reducers";
import App from "./components/App";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  reducers,
  composeEnhancers(applyMiddleware(reduxThunk, tokenMiddelware()))
);
...

Then in authActions i made the following function:

export const validateUser = () => async dispatch => {  
  dispatch({ type: "VALIDATE_TOKEN" });
  const headers = axios.defaults.headers.common;
  try {
    const response = await endpoint.get("api/v1/auth/validate_token", headers);
    const user = response.data.data;
    dispatch(setHeaders(response.headers));
    dispatch({ type: "SIGN_IN", payload: user });
  } catch (err) {
    console.log(err, "Missing Token. Please Log in.");
    dispatch({ type: "LOADED" });
  }
};

The setHeaders function just relays the headers that gets returned from the rails app to the middleware (via the action key), so that they are always available to the axios module (needed for every request).

Then you can make your own custom protected routes (assuming you are using react-router-dom), in which I call the validateUser action in ComponentDidMount. For my part I made three custom routes. AdminRoute, ProtectedRoute (for signed in users) and AuthRoute (which automatically redirects a signed in user away from the signup page). The syntax is a little obscure but here is the ProtectedRoute.js:

import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import { validateUser } from "../actions/authActions";

export class ProtectedRoute extends React.Component {
  componentDidMount = () => {
    this.props.validateUser();
  };

  render() {
    const {
      isSignedIn,
      isDOMLoaded,
      component: Component,
      ...rest
    } = this.props;

    if (isDOMLoaded) {
      return (
        <Route
          {...rest}
          render={props =>
            isSignedIn ? (
              <Component {...props} />
            ) : (
              <Redirect
                to={{ pathname: "/auth", state: { from: props.location } }}
              />
            )
          }
        />
      );
    } else {
      return null;
    }
  }
}

const MapStateToProps = state => {
  return {
    isSignedIn: state.currentUser.isSignedIn,
    isDOMLoaded: state.currentUser.isDOMLoaded
  };
};

export default connect(
  MapStateToProps,
  { validateUser }
)(ProtectedRoute);

You can probably omit IsDOMLoaded. Don't remember why I included that.

Then in your app.js where you define your routes:

import React from "react";
import { Router, Switch, Route } from "react-router-dom";
import history from "../history";

...
import AdminRoute from "./AdminRoute";
import ProtectedRoute from "./ProtectedRoute";
import AuthRoute from "./AuthRoute";
import LandingPage from "./LandingPage";
import Signup from "./auth/Signup";
...

const Routes = () => (
  <div className="ui container">
    <Router history={history}>
      <div>
        <Header />
        <Switch>
          <ProtectedRoute path="/" exact component={LandingPage} />
          <ProtectedRoute path="/profile" exact component={UserProfile} />
          ...
          <AdminRoute path="/admin" exact component={Dashboard} />
          ...     
          <AuthRoute path="/auth" exact component={AuthPage} />
          ...     
        </Switch>
        <Footer />
      </div>
    </Router>
  </div>
);

const App = () => {
  return Routes();
};

export default App;

@tochman
Copy link

tochman commented Mar 5, 2019

Awesome solution.

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

No branches or pull requests

3 participants