@react-solutions/request
v1.2.0
Published
A request library for react
Maintainers
Readme
@react-solutions/request
📦 Installation
npm install @react-solutions/requestPeer Dependencies
This package requires React 16.8+ and axios:
npm install react react-dom axios🚀 Features
- ✅ REST API Support - GET, POST, PUT, PATCH, DELETE methods
- ✅ GraphQL Support - Query and Mutation operations
- ✅ Built-in State Management - Automatic loading and error states using React hooks
- ✅ Request Caching - Store and retrieve request data by name
- ✅ Refetch Support - Easy refetching for GET and GraphQL queries
- ✅ TypeScript Support - Full TypeScript definitions included
- ✅ Request/Response Interceptors - Customize requests and responses
- ✅ Authorization Token Management - Easy authentication handling
- ✅ Configurable Base URL - Set and update base URLs dynamically
- ✅ Session Storage - Automatic persistence of configuration and request data
- ✅ Error Handling - Comprehensive error processing with detailed error objects
- ✅ Callback Support - onSuccess and onError callbacks for each request
📚 Quick Start
1. Basic Setup
First, configure your request instance:
import { createConfig } from "@react-solutions/request";
const requestInstance = createConfig({
baseURL: "https://api.example.com",
config: {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
timeout: 5000,
},
});
export default requestInstance;2. Using Request Provider (Optional)
Wrap your app with RequestProvider to use a custom axios instance:
import { RequestProvider } from "@react-solutions/request";
import requestInstance from "./config";
function App() {
return (
<RequestProvider client={requestInstance}>
<YourComponents />
</RequestProvider>
);
}Note: The RequestProvider uses the client prop.
3. Making REST API Calls
import { get, post, put, patch, del } from "@react-solutions/request";
function MyComponent() {
// GET Request (returns [requestFunction, state, refetchFunction])
const [getUsers, getState, refetchUsers] = get("/users", {
onSuccess: (data, payload) => {
console.log("Success:", data);
},
onError: (error, payload) => {
console.log("Error:", error);
},
name: "getUsers", // Optional: name for caching/retrieval
});
// POST Request (returns [requestFunction, state])
const [createUser, postState] = post("/users", {
onSuccess: (data, payload) => {
console.log("User created:", data);
},
name: "createUser", // Optional: name for caching
});
const handleGetUsers = () => {
getUsers({ page: 1, limit: 10 })
.then((res) => console.log("Response:", res))
.catch((err) => console.log("Error:", err));
};
const handleRefetch = () => {
// Refetch with same parameters
refetchUsers()
.then((res) => console.log("Refetched:", res))
.catch((err) => console.log("Error:", err));
};
const handleCreateUser = () => {
createUser({ name: "John Doe", email: "[email protected]" })
.then((res) => console.log("Created:", res))
.catch((err) => console.log("Error:", err));
};
return (
<div>
<button onClick={handleGetUsers}>Get Users</button>
<button onClick={handleRefetch}>Refetch Users</button>
<button onClick={handleCreateUser}>Create User</button>
{getState.isLoading && <p>Loading...</p>}
{getState.error && <p>Error: {getState.error?.message}</p>}
{getState.data && <pre>{JSON.stringify(getState.data, null, 2)}</pre>}
</div>
);
}4. Making GraphQL Calls
import { gqlQuery, gqlMutation } from "@react-solutions/request";
const GET_USERS = `
query GetUsers($limit: Int) {
users(limit: $limit) {
id
name
email
}
}
`;
const CREATE_USER = `
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
`;
function GraphQLComponent() {
// GraphQL Query (returns [requestFunction, state, refetchFunction])
const [getUsers, queryState, refetchUsers] = gqlQuery(GET_USERS, {
onSuccess: (data) => console.log("Query result:", data),
onError: (error) => console.log("Query error:", error),
name: "getUsers", // Optional: name for caching
});
// GraphQL Mutation (returns [requestFunction, state])
const [createUser, mutationState] = gqlMutation(CREATE_USER, {
onSuccess: (data) => console.log("Mutation result:", data),
name: "createUser", // Optional: name for caching
});
const handleQuery = () => {
getUsers({ limit: 10 })
.then((res) => console.log("Users:", res))
.catch((err) => console.log("Error:", err));
};
const handleMutation = () => {
createUser({ name: "Jane Doe", email: "[email protected]" })
.then((res) => console.log("Created:", res))
.catch((err) => console.log("Error:", err));
};
return (
<div>
<button onClick={handleQuery}>Get Users</button>
<button onClick={handleMutation}>Create User</button>
{queryState.isLoading && <p>Loading...</p>}
{queryState.error && <p>Error: {queryState.error?.message}</p>}
{queryState.data && <pre>{JSON.stringify(queryState.data, null, 2)}</pre>}
</div>
);
}📖 API Reference
Configuration Methods
createConfig(options)
Creates a new request instance with the specified configuration. The configuration is automatically persisted.
Parameters:
baseURL(string, required): Base URL for all requestsconfig(RequestConfig, optional): Additional configuration
Returns: RequestInstance (RequestInstance)
Example:
import { createConfig } from "@react-solutions/request";
const instance = createConfig({
baseURL: "https://api.example.com",
config: {
headers: {
Authorization: "Bearer token123",
},
timeout: 5000,
},
});updateConfig(options)
Updates the existing request configuration and recreates the request instance. The updated configuration.
Parameters:
baseURL(string, optional): New base URL (uses existing if not provided)config(RequestConfig, optional): Updated configuration (merges with existing)
Example:
import { updateConfig } from "@react-solutions/request";
// Update base URL
updateConfig({
baseURL: "https://new-api.example.com",
});
// Update configuration
updateConfig({
baseURL: "https://api.example.com",
config: {
headers: {
"X-Custom-Header": "value",
},
},
});setRequestInstance(requestInstance)
Sets a custom request instance (RequestInstance) to be used by all request methods.
Parameters:
requestInstance(RequestInstance): Custom instance
Example:
import { setRequestInstance } from "@react-solutions/request";
import axios from "axios";
const customInstance = axios.create({
baseURL: "https://api.example.com",
timeout: 10000,
});
setRequestInstance(customInstance);Authorization
authorizationToken(token)
Sets the authorization token for all requests. The token is automatically added to the Authorization header and persisted.
Parameters:
token(string): Authorization token (with or without "Bearer" prefix)
Example:
import { authorizationToken } from "@react-solutions/request";
// Token will be automatically added as Authorization header
authorizationToken("Bearer your-token-here");
// or
authorizationToken("your-token-here"); // Bearer prefix is optionalInterceptors
requestInterceptor(onFulfilled, onRejected?)
Adds a request interceptor to modify requests before they are sent.
Parameters:
onFulfilled(function): Callback when request is successful - receives config object, must return configonRejected(function, optional): Callback when request fails - receives error object
Example:
import { requestInterceptor } from "@react-solutions/request";
requestInterceptor(
(config) => {
// Modify request config
config.headers["X-Custom-Header"] = "value";
return config;
},
(error) => {
// Handle request error
return Promise.reject(error);
}
);responseInterceptor(onFulfilled, onRejected?)
Adds a response interceptor to modify responses or handle errors.
Parameters:
onFulfilled(function): Callback when response is successful - receives response object, must return responseonRejected(function, optional): Callback when response fails - receives error object
Example:
import { responseInterceptor } from "@react-solutions/request";
responseInterceptor(
(response) => {
// Modify response
return response;
},
(error) => {
// Handle response error (e.g., token expiry)
if (error.response?.status === 401) {
// Redirect to login
}
return Promise.reject(error);
}
);REST API Methods
All REST methods, return an array containing the request function and state.
get(url, props)
Creates a GET request hook. Returns 3 items: [requestFunction, state, refetchFunction].
Parameters:
url(string): API endpoint (relative to baseURL if configured)props(object, optional):onSuccess(function, optional): Success callback(data, payload) => any- return value will be stored as dataonError(function, optional): Error callback(error, payload) => any- return value will be stored as errorname(string, optional): Name for storing request data (enables caching and retrieval)
Returns: [getRequest, state, refetchRequest]
getRequest(payload?, config?): Function to execute the requestpayload(any, optional): Query parameters objectconfig(RequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>
state: Request state objectrefetchRequest(): Function to refetch with the same parameters (only available ifnamewas provided)
Example:
const [getUsers, state, refetchUsers] = get("/users", {
onSuccess: (data) => {
console.log(data);
// Optionally transform data
return data.map((user) => ({ ...user, displayName: user.name }));
},
onError: (error) => console.error(error),
name: "getUsers", // Required for refetch
});
// Execute request with query parameters
getUsers({ page: 1, limit: 10 }, { timeout: 5000 })
.then((res) => console.log(res))
.catch((err) => console.error(err));
// Refetch with same parameters
refetchUsers()
.then((res) => console.log("Refetched:", res))
.catch((err) => console.error(err));post(url, props)
Creates a POST request hook. Returns 2 items: [requestFunction, state].
Parameters:
url(string): API endpointprops(object, optional): Same asget
Returns: [postRequest, state]
postRequest(payload?, config?): Function to execute the requestpayload(any, optional): Request bodyconfig(AxiosRequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>
Example:
const [createUser, state] = post("/users", {
onSuccess: (data) => console.log(data),
name: "createUser",
});
createUser({ name: "John", email: "[email protected]" })
.then((res) => console.log(res))
.catch((err) => console.error(err));put(url, props)
Creates a PUT request hook for updating resources. Returns 2 items: [requestFunction, state].
Parameters:
url(string): API endpointprops(object, optional): Same asget
Returns: [putRequest, state]
putRequest(payload?, config?): Function to execute the requestpayload(any, optional): Request bodyconfig(AxiosRequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>
Example:
const [updateUser, state] = put("/users/1", {
onSuccess: (data) => console.log(data),
});
updateUser({ name: "Jane", email: "[email protected]" })
.then((res) => console.log(res))
.catch((err) => console.error(err));patch(url, props)
Creates a PATCH request hook for partial updates. Returns 2 items: [requestFunction, state].
Parameters:
url(string): API endpointprops(object, optional): Same asget
Returns: [patchRequest, state]
patchRequest(payload?, config?): Function to execute the requestpayload(any, optional): Request bodyconfig(AxiosRequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>
Example:
const [patchUser, state] = patch("/users/1", {
onSuccess: (data) => console.log(data),
});
patchUser({ name: "Updated Name" })
.then((res) => console.log(res))
.catch((err) => console.error(err));del(url, props)
Creates a DELETE request hook. Returns 2 items: [requestFunction, state].
Parameters:
url(string): API endpointprops(object, optional): Same asget
Returns: [deleteRequest, state]
deleteRequest(config?): Function to execute the requestconfig(AxiosRequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>
Example:
const [deleteUser, state] = del("/users/1", {
onSuccess: (data) => console.log(data),
});
deleteUser({ timeout: 5000 })
.then((res) => console.log(res))
.catch((err) => console.error(err));GraphQL Methods
gqlQuery(query, props)
Creates a GraphQL query hook. Sends POST request to /graphql endpoint. Returns 3 items: [requestFunction, state, refetchFunction].
Parameters:
query(string): GraphQL query stringprops(object, optional):onSuccess(function, optional): Success callback(data, payload) => anyonError(function, optional): Error callback(error, payload) => anyname(string, optional): Name for storing request data (enables caching and retrieval)
Returns: [queryRequest, state, refetchRequest]
queryRequest(variables?, config?): Function to execute the queryvariables(any, optional): GraphQL variables objectconfig(RequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>(response.data contains the GraphQL data)
state: Request state objectrefetchRequest(): Function to refetch with the same variables (only available ifnamewas provided)
Example:
const GET_USERS = `
query GetUsers($limit: Int) {
users(limit: $limit) {
id
name
}
}
`;
const [getUsers, state, refetchUsers] = gqlQuery(GET_USERS, {
onSuccess: (data) => console.log(data),
name: "getUsers", // Required for refetch
});
getUsers({ limit: 10 })
.then((res) => console.log(res))
.catch((err) => console.error(err));
// Refetch with same variables
refetchUsers()
.then((res) => console.log("Refetched:", res))
.catch((err) => console.error(err));gqlMutation(mutation, props)
Creates a GraphQL mutation hook. Sends POST request to /graphql endpoint. Returns 2 items: [requestFunction, state].
Parameters:
mutation(string): GraphQL mutation stringprops(object, optional): Same asgqlQuery
Returns: [mutationRequest, state]
mutationRequest(variables?, config?): Function to execute the mutationvariables(any, optional): GraphQL variables objectconfig(RequestConfig, optional): Additional request config- Returns:
Promise<SuccessResponse>(response.data contains the GraphQL data)
Example:
const CREATE_USER = `
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
`;
const [createUser, state] = gqlMutation(CREATE_USER, {
onSuccess: (data) => console.log(data),
});
createUser({ name: "John", email: "[email protected]" })
.then((res) => console.log(res))
.catch((err) => console.error(err));Request Retrieval
retrieve(name, callback?)
Retrieves the response data from a previously made request by name. Useful for accessing cached request data without making a new request.
Parameters:
name(string): The name of the stored request (must match thenameprop used when creating the request)callback(function, optional): Optional callback to process the response before returning(res) => any
Returns: The response data from the stored request, or null if not found
Example:
import { retrieve } from "@react-solutions/request";
// Get stored request data
const userData = retrieve("getUsers");
// Get and transform stored request data
const transformedData = retrieve("getUsers", (data) => {
return data.map((user) => ({ ...user, displayName: user.name }));
});State Object
Each request method returns a state object with the following properties:
{
isLoading: boolean; // True when request is in progress
error: { // Error object (null initially)
status: number;
message: string;
data: any;
statusText: string;
code: string;
name: string;
} | null;
data: any; // Response data (null initially)
}Note: The state object does not include isSuccess or isError boolean flags. Instead, check state.data !== null for success and state.error !== null for errors.
RequestProvider Component
The RequestProvider component allows you to use a custom request instance throughout your app.
Props:
client(RequestInstance): Custom axios instancechildren(ReactNode): Child components
Example:
import { RequestProvider } from "@react-solutions/request";
import axios from "axios";
const customInstance = axios.create({
baseURL: "https://api.example.com",
headers: {
"X-Custom-Header": "value",
},
});
function App() {
return (
<RequestProvider client={customInstance}>
<YourComponents />
</RequestProvider>
);
}useRequest Hook
The useRequest hook provides access to the request instance from the RequestProvider context.
Returns: The request instance (AxiosInstance) from the context
Example:
import { useRequest } from "@react-solutions/request";
function MyComponent() {
const requestInstance = useRequest();
// Use the instance directly if needed
requestInstance.get("/custom-endpoint").then((res) => console.log(res));
}🎯 Usage Examples
Complete REST API Example
import { get, post, updateConfig, authorizationToken } from "@react-solutions/request";
import { useEffect } from "react";
function UsersComponent() {
const [getUsers, getState, refetchUsers] = get("/users", {
onSuccess: (data) => {
console.log("Users loaded:", data);
},
onError: (error) => {
console.error("Failed to load users:", error);
},
name: "getUsers", // Enable caching and refetch
});
const [createUser, postState] = post("/users", {
onSuccess: (data) => {
console.log("User created:", data);
// Refresh users list
refetchUsers();
},
name: "createUser",
});
useEffect(() => {
// Configure base URL on mount
updateConfig({
baseURL: "https://jsonplaceholder.typicode.com",
});
// Set auth token if available
const token = localStorage.getItem("authToken");
if (token) {
authorizationToken(token);
}
// Load users on mount
getUsers({ _limit: 10 });
}, []);
const handleCreateUser = () => {
createUser({
name: "New User",
email: "[email protected]",
});
};
return (
<div>
<button
onClick={handleCreateUser}
disabled={postState.isLoading}
>
{postState.isLoading ? "Creating..." : "Create User"}
</button>
<button onClick={() => refetchUsers()}>Refresh</button>
{getState.isLoading && <p>Loading users...</p>}
{getState.error && <p>Error: {getState.error?.message}</p>}
{getState.data && (
<ul>
{getState.data.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}Complete GraphQL Example
import { gqlQuery, gqlMutation, updateConfig } from "@react-solutions/request";
import { useEffect } from "react";
const GET_POSTS = `
query GetPosts($limit: Int) {
posts(limit: $limit) {
id
title
body
}
}
`;
const CREATE_POST = `
mutation CreatePost($title: String!, $body: String!) {
createPost(title: $title, body: $body) {
id
title
body
}
}
`;
function PostsComponent() {
// Set GraphQL endpoint
useEffect(() => {
updateConfig({
baseURL: "https://api.example.com",
});
}, []);
const [getPosts, queryState, refetchPosts] = gqlQuery(GET_POSTS, {
onSuccess: (data) => console.log("Posts:", data),
onError: (error) => console.error("Query error:", error),
name: "getPosts", // Enable caching and refetch
});
const [createPost, mutationState] = gqlMutation(CREATE_POST, {
onSuccess: (data) => {
console.log("Post created:", data);
refetchPosts(); // Refresh list
},
name: "createPost",
});
const handleGetPosts = () => {
getPosts({ limit: 10 });
};
const handleCreatePost = () => {
createPost({
title: "New Post",
body: "Post content here",
});
};
return (
<div>
<button onClick={handleGetPosts}>Get Posts</button>
<button
onClick={handleCreatePost}
disabled={mutationState.isLoading}
>
Create Post
</button>
{queryState.isLoading && <p>Loading...</p>}
{queryState.error && <p>Error: {queryState.error?.message}</p>}
{queryState.data && (
<div>
{queryState.data.posts?.map((post: any) => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</div>
)}
</div>
);
}Using Request Caching and Retrieval
import { get, retrieve } from "@react-solutions/request";
function UserProfileComponent() {
const [getUser, userState] = get("/users/1", {
name: "getUserProfile", // Store with name
onSuccess: (data) => console.log("User loaded:", data),
});
useEffect(() => {
getUser();
}, []);
// In another component or function, retrieve cached data
const handleShowCachedData = () => {
const cachedUser = retrieve("getUserProfile");
if (cachedUser) {
console.log("Cached user:", cachedUser);
}
};
// Transform cached data
const handleShowTransformedData = () => {
const transformedUser = retrieve("getUserProfile", (data) => ({
...data,
fullName: `${data.firstName} ${data.lastName}`,
}));
console.log("Transformed user:", transformedUser);
};
return (
<div>
{userState.isLoading && <p>Loading...</p>}
{userState.data && <pre>{JSON.stringify(userState.data, null, 2)}</pre>}
<button onClick={handleShowCachedData}>Show Cached Data</button>
<button onClick={handleShowTransformedData}>Show Transformed Data</button>
</div>
);
}Using Interceptors for Global Error Handling
import { requestInterceptor, responseInterceptor, updateConfig } from "@react-solutions/request";
// Setup global interceptors
requestInterceptor((config) => {
// Add timestamp to all requests
config.headers["X-Request-Time"] = Date.now().toString();
return config;
});
responseInterceptor(
(response) => {
// Log successful responses
console.log("Response:", response);
return response;
},
(error) => {
// Handle global errors
if (error.response?.status === 401) {
// Token expired, redirect to login
window.location.href = "/login";
} else if (error.response?.status === 403) {
// Forbidden
console.error("Access forbidden");
} else if (error.response?.status >= 500) {
// Server error
console.error("Server error:", error);
}
return Promise.reject(error);
}
);
// Configure base URL
updateConfig({
baseURL: "https://api.example.com",
});🔧 Error Handling
Errors are automatically processed and include the following structure:
{
status: number; // HTTP status code (default: 500)
message: string; // Error message from response or fallback
data: any; // Full error response data
statusText: string; // HTTP status text
code: string; // Error code
name: string; // Error name
}Error messages are extracted in the following priority:
response.data.detailresponse.data.messageerror.message- Default: "An unknown error occurred"
For GraphQL errors, the first error message from the errors array is used.
Success Response Structure
Successful requests return a promise that resolves to:
{
data: any; // Response data (or transformed data from onSuccess callback)
status: number; // HTTP status code
statusText: string; // HTTP status text
payload: any; // Original payload/variables sent with the request
}💾 Session Storage
The library automatically persists the following to session storage:
- Base URL configuration
- Request configuration
- Authorization token
- Request data (when
nameprop is provided)
This allows the configuration to persist across page refreshes within the same browser session. To clear the stored data:
// The library doesn't expose a clear method, but you can manually clear:
sessionStorage.removeItem("requestManagerState");📝 TypeScript Support
This package is written in TypeScript and includes full type definitions. All exported functions and hooks are fully typed.
Type Definitions
import type {
RequestState,
ErrorResponse,
RequestProps,
SuccessResponse,
} from "@react-solutions/request";
// Use in your components
const [getUsers, state] = get<{ id: number; name: string }[]>("/users");
// state.data will be typed as { id: number; name: string }[] | null🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT
🔗 Related
💡 Tips
- Always configure baseURL: Use
updateConfigorcreateConfigto set your base URL - Use interceptors for global logic: Add authentication tokens, logging, or error handling
- Leverage state objects: Use
isLoading,error, anddatafor UI state management - Handle errors properly: Use both
onErrorcallbacks and.catch()for comprehensive error handling - GraphQL errors: GraphQL errors are automatically converted to HTTP-like error objects for consistent handling
- Use names for caching: Provide a
nameprop to enable request caching and refetch functionality - Refetch support: GET and GraphQL queries support refetching when a
nameis provided - Session persistence: Configuration and request data are automatically saved to session storage
Made with ❤️ for React developers
