React Query Authentication Hooks
react-query-auth is a specialized library designed to streamline user authentication management within React applications by leveraging `@tanstack/react-query`. It provides a `configureAuth` function that acts as a factory, generating custom hooks (`useUser`, `useLogin`, `useRegister`, `useLogout`) and an `AuthLoader` component. These utilities abstract away the complexities of integrating authentication state with React Query's caching mechanisms, treating user data as server state. The current stable version is `2.4.3`. While no explicit release cadence is stated, the versioning suggests active maintenance and development. Its key differentiator lies in its deep integration with React Query, offering a consistent approach to manage authentication alongside other server-derived data, reducing boilerplate for common authentication flows. It requires users to provide their own API interaction functions for fetching, logging in, registering, and logging out, making it adaptable to any backend or authentication method.
Common errors
-
Error: No QueryClient set, use QueryClientProvider to set one in the tree.
cause The application is attempting to use `react-query-auth` hooks or components without a `QueryClientProvider` higher in the component tree.fixWrap the root of your application (or the relevant component subtree) with `<QueryClientProvider client={new QueryClient()}>...</QueryClientProvider>`. -
TypeError: (0 , react_query_auth__WEBPACK_IMPORTED_MODULE_0__.configureAuth) is not a function
cause Incorrect import syntax, typically using CommonJS `require()` or a default import for a named export in an ES module environment.fixEnsure `configureAuth` is imported using named import syntax: `import { configureAuth } from 'react-query-auth';`. -
Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'email')
cause The `userFn` or `loginFn` provided to `configureAuth` is not returning a user object in the expected format (e.g., `{ email: '...' }`), or the API call failed to resolve successfully.fixVerify that your `userFn` and `loginFn` correctly return a Promise that resolves to an object containing the user's data or `null` for an unauthenticated state. Debug your API response to ensure it matches expectations. -
Query function returned a non-Promise. Did you forget to 'await' or return a Promise?
cause One of the authentication functions (`userFn`, `loginFn`, `registerFn`, `logoutFn`) passed to `configureAuth` did not return a Promise.fixEnsure that all functions provided to `configureAuth` (e.g., `userFn`, `loginFn`) always return a Promise, even if it's an immediately resolved one like `Promise.resolve(data)`.
Warnings
- gotcha All `react-query-auth` hooks and components, including the output of `configureAuth`, *must* be used within a `@tanstack/react-query` `QueryClientProvider`.
- gotcha This library requires you to provide the actual API interaction functions (`userFn`, `loginFn`, `registerFn`, `logoutFn`). It does not handle network requests or authentication methods (e.g., JWT, OAuth) itself.
- breaking The library explicitly lists `@tanstack/react-query` as a peer dependency. Using an incompatible or significantly older version may lead to runtime errors or unexpected behavior.
- gotcha The `userKey` option in `configureAuth` defaults to `['authenticated-user']`. If you manage multiple independent authentication systems within a single React Query instance, these keys could conflict, leading to incorrect user state.
Install
-
npm install react-query-auth -
yarn add react-query-auth -
pnpm add react-query-auth
Imports
- configureAuth
const configureAuth = require('react-query-auth').configureAuth;import { configureAuth } from 'react-query-auth'; - AuthLoader
const { AuthLoader } = require('react-query-auth');import { AuthLoader } from 'react-query-auth'; - { useUser, useLogin, useRegister, useLogout }
const { useUser, useLogin, useRegister, useLogout } = configureAuth({...});
Quickstart
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { configureAuth, AuthLoader } from 'react-query-auth';
const queryClient = new QueryClient();
// Mock API functions for demonstration
const api = {
get: async (url: string) => {
if (url === '/me') {
// Simulate authenticated user
const user = localStorage.getItem('mock-user');
return user ? JSON.parse(user) : null;
}
throw new Error('Not found');
},
post: async (url: string, data?: any) => {
if (url === '/login' && data.email === 'test@example.com' && data.password === 'password') {
const user = { id: '123', email: data.email };
localStorage.setItem('mock-user', JSON.stringify(user));
return user;
}
if (url === '/register') {
const user = { id: '456', email: data.email };
localStorage.setItem('mock-user', JSON.stringify(user));
return user;
}
if (url === '/logout') {
localStorage.removeItem('mock-user');
return null;
}
throw new Error('API Error');
}
};
const { useUser, useLogin, useLogout } = configureAuth({
userFn: () => api.get('/me'),
loginFn: (credentials) => api.post('/login', credentials),
registerFn: (credentials) => api.post('/register', credentials),
logoutFn: () => api.post('/logout')
});
function UserInfo() {
const user = useUser();
const logout = useLogout();
if (user.isLoading) {
return <div>Loading user info...</div>;
}
if (user.error) {
return <div style={{ color: 'red' }}>Error: {user.error.message}</div>;
}
if (!user.data) {
return (
<div>
<div>Not logged in</div>
<button onClick={() => useLogin().mutate({ email: 'test@example.com', password: 'password' })}>Login</button>
</div>
);
}
return (
<div>
<div>Logged in as {user.data.email}</div>
<button disabled={logout.isLoading} onClick={() => logout.mutate({})}>
Log out
</button>
</div>
);
}
function AuthScreen() {
const login = useLogin();
const register = useRegister();
const handleLogin = () => login.mutate({ email: 'test@example.com', password: 'password' });
const handleRegister = () => register.mutate({ email: 'new@example.com', password: 'password' });
return (
<div>
<h2>Authentication</h2>
<button onClick={handleLogin}>Login as Test User</button>
<button onClick={handleRegister}>Register New User</button>
{login.error && <div style={{ color: 'red' }}>Login Error: {login.error.message}</div>}
{register.error && <div style={{ color: 'red' }}>Register Error: {register.error.message}</div>}
</div>
);
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<h1>My Auth App</h1>
<AuthLoader
renderLoading={() => <div>Initial authentication check...</div>}
renderUnauthenticated={() => <AuthScreen />}
>
<UserInfo />
</AuthLoader>
</QueryClientProvider>
);
}
export default App;