SuperTokens: Cannot use useSessionContext outside auth wrapper components

The SuperTokens error “Uncaught Error: Cannot use useSessionContext outside auth wrapper components” is caused by trying to use useSessionContext outside the SuperTokensWrapper component. To fix this, refactor your code so that all components that need to use useSessionContext are located within the SuperTokensWrapper tags.

This error was discovered while I was working on the SuperTokens example. If you’re just getting into SuperTokens, I recommend you check out that article and its associated GitHub project.

Let’s look at a scenario that can cause this issue.

Problem: you’re using useSessionContext outside the SuperTokensWrapper

Below is some code from our SuperTokens example article:

import { Provider } from "urql";
import { urqlClient } from "./utils";

import SuperTokens from "supertokens-auth-react";
import { SuperTokensWrapper, getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import { signOut } from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session';

import {
    BrowserRouter,
    Routes,
    Route,
    Link,
    useNavigate,
} from "react-router-dom";
import * as reactRouterDom from "react-router-dom";

import HomePage from "./HomePage";

SuperTokens.init({
    appInfo: {
        appName: "example",
        apiDomain: `http://${process.env.REACT_APP_HOSTNAME}:8000`,
        websiteDomain: `http://${process.env.REACT_APP_HOSTNAME}:3000`,
        apiBasePath: "/auth",
        websiteBasePath: "/auth",
    },
    recipeList: [
        EmailPassword.init(),
        Session.init()
    ]
});


function App() {
    const {loading, doesSessionExist} = useSessionContext();
    const navigate = useNavigate();
    const onSignOut = async () => {
        await signOut();
        navigate("/auth");
    }
    return (
        <SuperTokensWrapper>
            <Provider value={urqlClient}>
                <BrowserRouter>
                    <div id="main">
                        <div id="menu">
                            <ul>
                                <li><Link to="/">Home</Link></li>
                                <li>
                                    { !loading &&
                                        doesSessionExist
                                            ? <button onClick={onSignOut}>Sign Out</button>
                                            : <Link to="/auth">Sign In</Link>
                                    }
                                </li>
                            </ul>
                        </div>
                        <Routes>
                            <Route path="/" element={
                                <SessionAuth>
                                    <HomePage />
                                </SessionAuth>
                            } />
                            {getSuperTokensRoutesForReactRouterDom(reactRouterDom)}
                        </Routes>
                    </div>
                </BrowserRouter>
            </Provider>
        </SuperTokensWrapper>
    );
}

export default App;
JavaScript

This is the top level App.js file that is generated by create-react-app. It’s usually a good place to put the components and configuration needed for the various plug-ins your app uses, as well as the top-level layout such as the Routes tags if you’re using react-router-dom, the menu, etc.. Normally that’s the case, but when running this app in a browser, I got a blank page and the following error in the dev console:

This took a while to figure out, but the problem ended up being the following lines:

function App() {
    const {loading, doesSessionExist} = useSessionContext();
    const navigate = useNavigate();
    const onSignOut = async () => {
        await signOut();
        navigate("/auth");
    }
    return (
        <SuperTokensWrapper>
            <Provider value={urqlClient}>
                <BrowserRouter>
                    <div id="main">
                        <div id="menu">
                            <ul>
                                <li><Link to="/">Home</Link></li>
                                <li>
                                    { !loading &&
                                        doesSessionExist
                                            ? <button onClick={onSignOut}>Sign Out</button>
                                            : <Link to="/auth">Sign In</Link>
                                    }
                                </li>
                            </ul>
                        </div>
                        <Routes>
                            <Route path="/" element={
                                <SessionAuth>
                                    <HomePage />
                                </SessionAuth>
                            } />
                            {getSuperTokensRoutesForReactRouterDom(reactRouterDom)}
                        </Routes>
                    </div>
                </BrowserRouter>
            </Provider>
        </SuperTokensWrapper>
    );
}

export default App;
JavaScript

It seems obvious in hindsight, but you can’t use useSessionContext from outside the SuperTokensWrapper tags because there’s no context for it to access.

Next we’ll look at how I refactored the code to fix the error.

Solution: move your component that calls useSessionContext inside the SuperTokensWrapper

As I already mentioned, we need to put our useSessionContext hook inside the SuperTokensWrapper tag. Unfortunately, this means we need another top-level component besides App.js. I chose to call it Main.js.

Here’s our new App.js file:

import { Provider } from "urql";
import { urqlClient } from "./utils";
import { BrowserRouter } from "react-router-dom";

import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
import { SuperTokensWrapper } from "supertokens-auth-react";

import Main from "./Main";

SuperTokens.init({
    appInfo: {
        appName: "example",
        apiDomain: `http://${process.env.REACT_APP_HOSTNAME}:8000`,
        websiteDomain: `http://${process.env.REACT_APP_HOSTNAME}:3000`,
        apiBasePath: "/auth",
        websiteBasePath: "/auth",
    },
    recipeList: [
        EmailPassword.init(),
        Session.init()
    ]
});


function App() {
    return (
        <SuperTokensWrapper>
            <Provider value={urqlClient}>
                <BrowserRouter>
                    <Main />
                </BrowserRouter>
            </Provider>
        </SuperTokensWrapper>
    );
}

export default App;
App.js

As you can see, we took out the useSessionContext hook and all associated stuff and placed it in Main.js so that it will actually work.

Here’s Main.js:

import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react";
import { signOut } from "supertokens-auth-react/recipe/emailpassword";
import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session';
import {
    Routes,
    Route,
    Link,
    useNavigate,
} from "react-router-dom";
import * as reactRouterDom from "react-router-dom";

import HomePage from "./HomePage";

export default function Main() {
    const {loading, doesSessionExist} = useSessionContext();
    const navigate = useNavigate();
    const onSignOut = async () => {
        await signOut();
        navigate("/auth");
    }

    return (
        <div id="main">
            <div id="menu">
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li>
                        { !loading &&
                            doesSessionExist
                                ? <button onClick={onSignOut}>Sign Out</button>
                                : <Link to="/auth">Sign In</Link>
                        }
                    </li>
                </ul>
            </div>
            <Routes>
                <Route path="/" element={
                    <SessionAuth>
                        <HomePage />
                    </SessionAuth>
                } />
                {getSuperTokensRoutesForReactRouterDom(reactRouterDom)}
            </Routes>
        </div>
    );
}
Main.js

This does add some more lines of code to your app, but it makes it possible to use useSessionContext, and makes sure that the rest of your components actually exist within the context of SuperTokensWrapper.

Conclusion

The SuperTokens React SDK requires that all calls to useSessionContext occur from inside the SuperTokensWrapper tags. This is necessary because, if you don’t, there’s no context for the hook to use. The context is SuperTokensWrapper. Fixing this error usually means refactoring your top-level component, be it App.js if you’re using create-react-app or otherwise, and creating another “top” level component where your actual app logic resides.