Invoke hook from React Router data router action

Advertisements

I am using React Router 6.4.3 with the new data routers.

A 3rd party authentication library exposes an authentication context and a login function through a hook, like:

const { AuthenticationContext, login } = useAuthentication();

I already managed to wrap the routes into the AuthenticationContext by using a pathless route in createBrowserRouter.

However, I fail to understand how I could make use of route actions to perform the login and redirect the user afterwards. As configuration and action are now outside of a functional component, I cannot make use of the useAuthentication hook.

My configuration looks like this:

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { AuthProvider } from 'auth';
import Login, { action as loginAction } from './routes/login';

const router = createBrowserRouter([
    {
        element: <AuthProvider />,
        children: [
            {
                path: '/login',
                element: <Login />,
                action: loginAction,
            },
        ],
    },
]);

function App() {
    return (
        <RouterProvider router={router} />
    );
}

With loginAction currently only a stub:

const action = async ({ request, params }) => {
    const formData = await request.formData();
    const updates = Object.fromEntries(formData);

    // How to invoke login() method exposed by useAuthentication() hook here?

}

The AuthenticationContext and associated methods roughly look like this:

const loginApi = async (email, password) => {
   // ajax call to login endpoint
}

const AuthContext = React.createContext(null);

const AuthProvider = () => {
    const [jwt, setJwt] = useState(null);
    const navigate = useNavigate();

    const login = async (email, password) => {
        const jwt = await loginApi(email, password);
        setJwt(jwt);
        navigate('/dashboard');
    }

    const contextValue = {
        jwt,
        login: handleLogin,
    }
 
    return (
        <AuthContext.Provider value={contextValue}>
            <Outlet />
        </AuthContext.Provider>
    );
}

const useAuth = () => {
    return useContext(AuthContext);
}

export default AuthProvider;
export { useAuth };

Is there a way to make this work with the data routers without touching the auth lib?

>Solution :

You very likely need to refactor the code a bit to allow the App component to use the useAuthentication hook so it can access the login function so it can be passed to the loginAction handler.

Example:

‘./routes/login’

import { redirect } from "react-router-dom";

const action = ({ login }) => async ({ request, params }) => {
  const formData = await request.formData();
  const updates = Object.fromEntries(formData);

  // call login, and redirect upon success
  await login(...);
  ...
  redirect('/dashboard');
};

App

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Login, { action as loginAction } from './routes/login';

function App() {
  const { login } = useAuthentication();

  const router = createBrowserRouter([
    ....,
    {
      path: '/login',
      element: <Login />,
      action: loginAction({ login }),
    },
    ....
  ]);

  return (
    <RouterProvider router={router} />
  );
}

index.js

import { AuthProvider } from 'auth';

...

<AuthProvider>
  </App />
</AuthProvider>

AuthenticationContext

const AuthContext = React.createContext(null);

const AuthProvider = ({ children }) => {
  const [jwt, setJwt] = useState(null);

  const login = async (email, password) => {
    const jwt = await loginApi(email, password);
    setJwt(jwt);
    // return success/fail for login action handler
  }

  const contextValue = {
    jwt,
    login: handleLogin,
  }
 
  return (
    <AuthContext.Provider value={contextValue}>
      {children}
    </AuthContext.Provider>
  );
}

Leave a ReplyCancel reply