npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@remate/core

v0.0.15

Published

remate is a React-based boilerplate for building internal tools, rapidly.

Downloads

177

Readme

Remate

Remate is a purpose-built React framework designed for developing CRUD-intensive web applications. It is ideal for enterprise-level use cases, including internal tools, admin panels, dashboards, B2B platforms, and other client-side applications.

Remate's core hooks and components streamline the development process by offering industry-standard solutions for crucial aspects of a project, including authentication, access control, routing, networking, and state management.

Overview

Authentication & Authorization

Remate offers a comprehensive solution for secure user management and fine-grained access control:

  • Authentication: Seamless integration with various authentication methods, including:

    • Social logins (e.g., Google, Facebook)
    • Email/password authentication
    • OAuth and custom providers
  • Advanced Authorization Mechanisms:

    • Role-Based Access Control (RBAC)
    • Permission-Based Access Control (PBAC)
    • Attribute-Based Access Control (ABAC)
    • Claim-Based Access Control (CBAC)
  • Encrypted Session Handling: Ensures secure storage of sensitive tokens and user data with encrypted local storage.

Data Fetching (asynchronous state management)

Built around react-query, the Data Fetching system ensures efficient communication with backend services. Key features include:

Query Handling:

  • Error Management: Centralized error tracking and user-friendly feedback mechanisms.
  • Loading State Management: Intuitive loading indicators with cancellation support for redundant or outdated requests.
  • Infinite Query Support: Handles paginated data fetching with ease, enabling smooth scrolling experiences.

Mutation Handling:

  • Optimistic Updates: Allows seamless user experiences by updating UI state before server confirmation.
  • Automatic Cache Invalidation: Ensures consistent data by refreshing dependent queries after mutations.

Routing and Navigation

  • Dynamic Navigation State: Facilitates the creation of breadcrumbs, page titles, and document titles based on route configuration.
  • Layout Configuration: Customizable layouts for individual routes to enhance modularity and reusability.
  • Secure Routing: Integrates access control directly into routing logic to secure sensitive routes.

Utilities

  • Reusable Helper Functions: Provides a library of utility functions to address common development tasks, such as formatting, data validation, and deep comparisons.
  • Custom React Hooks: Includes hooks for state management, side-effect handling, and component lifecycle needs.

Architecture

  • Headless Design: Adopts a UI-agnostic architecture to allow seamless integration with any UI framework or design system.
  • TypeScript-Driven Development: Ensures type safety and enhanced developer experience.
  • Extendable Modules: Offers a modular design, enabling developers to customize and extend functionalities based on project requirements.

Benefits

  • Developer Productivity: Streamlined workflows with pre-built modules and utilities.
  • Security and Reliability: Adherence to best practices in authentication, storage, and API communication.
  • Scalability: Designed to handle growing application demands with ease.
  • Flexibility: Supports diverse application requirements through modular and customizable architecture.

Quick Start Guide

The quickest way to start using Remate is through the Remate Boilerplate, which sets up a ready-to-use development environment with minimal effort.

Alternatively, you can manually integrate Remate into your existing project. Below is an example of setting up a basic application with Remate:

Setting Up Remate Manually

Installation

Start by installing the core Remate package and the necessary peer dependencies:

npm install @remate/core react react-dom react-router @tanstack/react-query

Create Your Application Entry Point:

This is the main entry point for the Remate application, which includes routing, layout management, authentication, and notifications.

import { Remate, RemateRoutesType, IAuthProviderAdapter, NotificationProviderType, ILayoutProps } from '@remate/core';
import { BrowserRouter } from 'react-router';
import { toast } from 'react-toastify';

import DefaultLayout from './layouts/DefaultLayout';
import CustomLayout from './layouts/CustomLayout';

import HomePage from './pages/HomePage';
import SignInPage from './pages/SignInPage';

import internalAuthProvider from './auth/internalAuthProvider';

const layouts = {
	// Define your application layouts here
	default: ({ config: configFromRouteConfig, children }: ILayoutProps) => (
		<DefaultLayout config={configFromRouteConfig}>{children}</DefaultLayout>
	),
	custom: CustomLayout1
};

const routes: RemateRoutesType = [
	// Define your application routes here
	{ path: '/', element: <HomePage />, privileges: 'admin', title: 'Home' },
	{
		path: '/signin',
		element: <SignInPage />,
		privileges: 'guest',
		title: 'sign in',
		layout: {
			style: 'custom',
			config: {
				/* Custom config for this layout, e.g., no sidebar, full screen */
			}
		}
	}
];

// Notification provider setup using react-toastify for example
const notificationProvider: NotificationProviderType = {
	open: ({ key, message, type }) => {
		if (toast.isActive(key as string)) {
			toast.update(key as string, {
				render: message,
				closeButton: true,
				autoClose: 5000,
				type
			});
		} else {
			toast(message, {
				toastId: key,
				type
			});
		}
	},
	close: (key) => toast.dismiss(key)
};

function App() {
	return (
		<BrowserRouter>
			<Remate
				routes={routes}
				notificationProvider={notificationProvider}
				layouts={layouts}
				defaultLayoutStyle="default"
				authProvider={internalAuthProvider}
			>
				{/* children components */}
			</Remate>
		</BrowserRouter>
	);
}

export default App;

Table of Contents

Authentication and Authorization

Authentication and authorization are foundational aspects of securing modern web applications. Remate provides a robust and extensible framework to handle these requirements seamlessly.

Authentication

Authentication is the process of verifying the identity of a user or client. It's a critical component of security, ensuring that only authorized users can access certain features or data within the application. Whether you are building a complex enterprise-level application or a simple CRUD interface, Remate's authentication system provides the necessary infrastructure to protect your pages and ensure that users interact with your application in a secure and controlled manner.

Remate's flexible architecture allows you to easily implement various authentication strategies like social logins (e.g., Google, Facebook), email/password authentication and OAuth and custom providers.

Authorization

Authorization is a key aspect of security and user experience in web applications. Whether you are building a complex enterprise-level application or a simple CRUD interface, Remate's authorization system provides the necessary infrastructure to protect your resources and ensure that users interact with your application in a secure and controlled manner.

Remate's flexible architecture allows you to easily implement various authorization strategies:

  • Role-Based Access Control (RBAC)
  • Permission-based Access Control (PBAC)
  • Claim-based Access Control (CBAC)
  • Attribute-Based Access Control (ABAC)

With any authorization solution.

Remate offers several features to help you implement authorization in your application:

  • Component-Level Control: Use the <CanAccess /> component to render elements conditionally based on access.
  • Programmatic Checks: The canAccess function (available in the useAuth hook) enables programmatic access control logic.
  • Route Security: control route access, ensuring users see only what they are authorized for.

Auth Provider

Remate can use any authentication backend, but you have to write an adapter for it. This adapter is called an authProvider. The authProvider is a simple object with methods that Remate calls to handle authentication and authorization.

IAuthProviderAdapter Interface Overview

The authProvider is defined by the IAuthProviderAdapter interface, which specifies the following methods:

interface IAuthProviderAdapter<TUser, TUserPrivileges, TRequestedPrivileges> {
	signInPageUrl: string;
	loginRedirectUrl: string;
	onError: (error: any) => Promise<OnErrorResponse> | OnErrorResponse | null | Promise<null>;
	initialCheck: () => Promise<CheckResponse<TUser>> | CheckResponse<TUser>;
	getUser: () => Promise<TUser> | TUser;
	signOut: (...args: any[]) => Promise<SignOutResponse> | SignOutResponse;
	signIn: (...args: any[]) => Promise<AuthActionResponse<TUser>> | AuthActionResponse<TUser>;
	initialUserState?: TUser | null;
	getUserPrivileges?: (user: TUser) => Promise<TUserPrivileges> | TUserPrivileges;
	canAccess?: (requestedPrivileges: TRequestedPrivileges, userPrivileges: TUserPrivileges) => boolean;
	signUp?: (...args: any[]) => Promise<AuthActionResponse<TUser>> | AuthActionResponse<TUser>;
	updateUser?: (userData: PartialDeep<TUser>, currentUser: TUser) => Promise<TUser> | TUser;
	checkingAuthFallback?: ReactNode;
	routeAccessDeniedFallback?: ReactNode;
}

Example Implementation

Here is a fictive but working implementation of an auth provider. It only accepts user “john” with password “123”.

import { IAuthProviderAdapter } from '@remate/core';

type Privileges = 'user' | 'guest';

interface IUser {
	id: string;
	fullName: string;
	role: Privileges;
}

interface ISignInCredentials {
	username: string;
	password: string;
}

const authProvider: IAuthProviderAdapter<IUser, Privileges> = {
	signInPageUrl: '/sign-in',
	loginRedirectUrl: '/',
	async signIn({ username, password }: ISignInCredentials) {
		if (username !== 'john' || password !== '123') {
			return {
				success: false,
				error: new Error('Login failed')
			};
		}

		localStorage.setItem('username', username);

		return {
			success: true,
			user: { id: username, fullName: username, role: 'user' }
		};
	},
	async signOut() {
		localStorage.removeItem('username');

		return { success: true };
	},
	async initialCheck() {
		const authenticated = localStorage.getItem('username') !== null;

		return { authenticated };
	},
	async getUser() {
		const username = localStorage.getItem('username');

		return { id: username, fullName: username, role: 'user' };
	},
	async getUserPrivileges(user) {
		return user.role;
	}
};

export default authProvider;
using authProvider

The following example demonstrates how to using authProvider and pass them to the Remate component:

import { Remate, ILayoutProps } from '@remate/core';
import { BrowserRouter } from 'react-router';
import authProvider from './auth/authProvider';

function App() {
	return (
		<BrowserRouter>
			<Remate authProvider={authProvider}>{/* Children components */}</Remate>
		</BrowserRouter>
	);
}

export default App;

Step-By-Step Guide for Auth Provider Implementation

signInPageUrl

The pathname to redirect users when they need to log in.

loginRedirectUrl

The default pathname to redirect users after a successful login.

onError

this method is called when you get an error response from the API. You can create your own business logic to handle the error such as refreshing the token, logging out the user, etc.

Example Implementing onError

Below is a basic example of how to define and use the onError method:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter } from '@remate/core';

export const authProvider: IAuthProviderAdapter = {
	onError(error) {
		if (error.response?.status === 401) {
			return { logout: true };
		}

		return null;
	}
	// Other methods like signOut, etc., would go here.
};

signIn

The signIn method is responsible for handling the logic of logging users into the application with the current authentication method. You will typically call this method using the signIn function provided by the useAuth hook.

When defining the signIn method within the authProvider, it should return a object based on SignInResponse interface. This response may include user data. If the user field is provided in the response, the getUser method will not be called after login.

Example: Implementing signIn

Below is a basic example of how to define and use the signIn method:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter, SecureLocalStorage } from '@remate/core';

export interface IUser {
	email: string;
}

export interface ISignInCredentials {
	email: string;
	password: string;
}

export const authProvider: IAuthProviderAdapter<IUser> = {
	async signIn({ email, password }: ISignInCredentials) {
		// Simplified example: Instead of sending a request, we're saving the credentials in localStorage.
		// In production, send a secure request to the server and store tokens securely.
		SecureLocalStorage.setItem('token', { email, password });
		alert('You are logged in!');

		return {
			success: true,
			user: { email }
		};
	}
	// Other methods like signOut, check, etc., would go here.
};
Login Page Implementation

Here’s an example of a basic login page that utilizes the signIn method from the useAuth hook:

import React from 'react';
import { useAuth } from '@remate/core';

export const LoginPage = () => {
	const { signIn } = useAuth();

	const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
		e.preventDefault();

		// Extract form data
		const formData = Object.fromEntries(new FormData(e.currentTarget).entries());

		// Call the signIn method
		signIn(formData);

		// Reset the form
		e.currentTarget.reset();
	};

	return (
		<div>
			<h1>Sign In</h1>
			<form onSubmit={(e) => onSubmit(e)}>
				<input
					type="email"
					name="email"
					placeholder="Email"
					required
				/>
				<input
					type="password"
					name="password"
					placeholder="Password"
					required
				/>
				<button type="submit">Submit</button>
			</form>
		</div>
	);
};

signUp

The signUp method is an optional function designed to handle the logic for registering new users in the application using the current authentication method. You can typically call this method via the signUp function provided by the useAuth hook.

When implementing the signUp method within the authProvider, it should return an object conforming to the SignInResponse interface. This response may include user data, and if the user field is provided, the getUser method will not be called after the user is logged in.

Note: After calling signUp (provided by useAuth), the user is authenticated and logged into the application in the same way as with signIn.

Example: Implementing signUp

Below is a simple example of how to define and use the signUp method:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter, SecureLocalStorage } from '@remate/core';

export interface IUser {
	email: string;
}

export interface ISignUpCredentials {
	email: string;
	password: string;
}

export const authProvider: IAuthProviderAdapter<IUser> = {
	async signUp({ email, password }: ISignUpCredentials) {
		// Simplified example: Instead of sending a request, credentials are saved directly in localStorage.
		// In a real-world application, send a secure request to the server and store tokens securely.
		SecureLocalStorage.setItem('token', { email, password });
		alert('You are now registered and logged in!');

		return {
			success: true,
			user: { email }
		};
	}
	// Additional methods like signIn, signOut, and check would go here.
};
Register Page Implementation

Here’s an example of a basic registration page that utilizes the signUp method from the useAuth hook:

import React from 'react';
import { useAuth } from '@remate/core';

export const RegisterPage = () => {
	const { signUp } = useAuth();

	const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
		e.preventDefault();

		// Extract form data
		const formData = Object.fromEntries(new FormData(e.currentTarget).entries());

		// Call the signUp method
		signUp(formData);

		// Reset the form
		e.currentTarget.reset();
	};

	return (
		<div>
			<h1>Register</h1>
			<form onSubmit={onSubmit}>
				<input
					type="email"
					name="email"
					placeholder="Email"
					required
				/>
				<input
					type="password"
					name="password"
					placeholder="Password"
					required
				/>
				<button type="submit">Submit</button>
			</form>
		</div>
	);
};

signOut

The signOut method is used to define the logout logic for the application with the current authentication strategy. You can call this method using the signOut function provided by the useAuth hook.

When implementing the signOut method within the authProvider, it should return an object conforming to the SignOutResponse interface. If the logout process completes successfully and you want the user to immediately log out, set the success property to true in the returned object. Alternatively, if you want to implement a redirect logic during logout, use the redirect property and provide the desired URL.

Additionally, you can define and pass any parameters needed for your signOut method.

Example: Implementing signOut

Below is a simple example of how to define and use the signOut method:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter, SecureLocalStorage } from '@remate/core';

export interface IUser {
	email: string;
}

export const authProvider: IAuthProviderAdapter<IUser> = {
	async signOut() {
		SecureLocalStorage.removeItem('token');
		alert('You are now logged out!');

		return {
			logout: true // Log the user out immediately
		};
	}
	// Additional methods like signIn, signUp, and check would go here.
};
Using the signOut Method

Here’s an example of how to use the signOut method from the useAuth hook within a component:

import React from 'react';
import { useAuth } from '@remate/core';
import { IUser } from '../authProvider';

export const AppToolbar = () => {
	const { signOut, user } = useAuth<IUser>();

	return (
		<div>
			<p>User Email: {user.email}</p>
			<button onClick={() => signOut()}>Sign Out</button>
		</div>
	);
};

initialCheck

The initialCheck method is a critical part of the authentication process, ensuring that the user's session remains valid after login or registration. This method verifies if the user is currently authenticated and provides the necessary data to maintain their session. It works in conjunction with the isAuthenticated property provided by the useAuth hook to determine the user's authentication status.

this method is used to check if the user is authenticated. It is internally called as an initializer. It is expected to return a resolved promise conforming to the CheckResponse type. The response should include:

  • authenticated: A boolean indicating the user's authentication status.
  • user (optional): User data. If this is provided, the getUser method will not be invoked after the check.
Example: Implementing the initialCheck Method

Below is an example of how to define the initialCheck method in the authProvider:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter, SecureLocalStorage } from '@remate/core';

export interface IUser {
	email: string;
}

export const authProvider: IAuthProviderAdapter<IUser> = {
	async initialCheck() {
		const token = SecureLocalStorage.getItem('token');

		if (token && token.email) {
			return {
				authenticated: true,
				user: { email: token.email }
			};
		}

		return {
			authenticated: false
		};
	}
	// Additional methods like signIn, signUp, and signOut would go here.
};
Using the isAuthenticated Property

Here’s an example of how to use the isAuthenticated property from the useAuth hook to manage user authentication status:

Example Component: App Toolbar
import React from 'react';
import { useAuth } from '@remate/core';
import { Link } from 'react-router';

import { IUser } from '../authProvider';

export const AppToolbar = () => {
	const { isAuthenticated, user, signOut } = useAuth<IUser>();

	return (
		<div>
			{isAuthenticated ? (
				<>
					<p>User Email: {user.email}</p>
					<button onClick={() => signOut()}>Sign Out</button>
				</>
			) : (
				<Link to="/sign-in">Sign In</Link>
			)}
		</div>
	);
};

getUser

The getUser method is essential for adapting application components to the current user's identity. For instance, a locking system may allow editing only if the current user is the lock owner. Similarly, user menus may display the current user's name and avatar.

This method is expected to return a resolved promise conforming to the TUser type from the IAuthProviderAdapter interface.

The getUser method is automatically invoked when the check, signIn, or signUp methods are executed without returning a user in their responses. Additionally, the refreshUser method can be used to internally re-fetch the user data, ensuring the user state reflects the latest data returned by getUser.

Example: Implementing the getUser Method

Below is an example of how to define the getUser method in the authProvider:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter, SecureLocalStorage } from '@remate/core';

export interface IUser {
	email: string;
}

export const authProvider: IAuthProviderAdapter<IUser> = {
	async getUser() {
		// Simplified example: Retrieve user data directly from localStorage.
		// In a real-world scenario, make a secure server request to fetch user details.
		const token = SecureLocalStorage.getItem('token');

		if (token && token.email) {
			return { email: token.email };
		}

		throw new Error('User is not authenticated');
	}
	// Additional methods like signIn, signUp, and signOut would go here.
};
Using the user Property and refreshUser method

Here’s an example of how to use the user property and refreshUser method from the useAuth hook to access and refetch user information:

Example Component: App Toolbar
import React from 'react';
import { useAuth } from '@remate/core';

import { IUser } from '../authProvider';

export const AppToolbar = () => {
	const { user, refreshUser } = useAuth<IUser>();

	return (
		<div>
			<p>User Email: {user.email}</p>
			<button onClick={() => refreshUser()}>Refresh User Data</button>
		</div>
	);
};

getUserPrivileges

The getUserPrivileges method allows you to define and retrieve the access privileges of a user, enabling you to check their permissions for interacting with system resources. The user object is passed as a parameter to this method and you can use it if needed.

This method must return a value based on the TPrivileges type from the IAuthProviderAdapter interface. The output of this method is primarily utilized in the authProvider.canAccess method to validate access to specific resources.

Example: Implementing getUserPrivileges

Below is an example of how to define the getUserPrivileges method within an authProvider:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter } from '@remate/core';

// Define privilege levels
export type Privileges = 'admin' | 'staff' | 'user';

// Define the user structure
export interface IUser {
	role: Privileges;
	email: string;
}

export const authProvider: IAuthProviderAdapter<IUser, Privileges> = {
	async getUserPrivileges(user) {
		// Return the user's role as their privilege
		return user.role;
	},
	// The `canAccess` method is pre-defined by default.
	// However, you can customize it as needed.
	canAccess(requestedPrivilege, userPrivileges) {
		// Example logic: Grant access if requested privilege matches the user's privilege
		return requestedPrivilege === userPrivileges;
	}
	// Additional methods like signIn, signUp, and signOut would go here.
};

canAccess

The canAccess method provides a flexible way to define custom logic for managing access to specific resources in your application. This method is versatile and can be used in multiple contexts, such as:

  • Component-Level Access Control: Using the CanAccess component.
  • Route Guards: Controlling access at the routing level.
  • Programmatic Access Checks: Through the canAccess method available in the useAuth hook.

It supports the implementation of various access control strategies, including RBAC (Role-Based Access Control), PBAC (Permission-Based Access Control), and others.

Parameters

The method takes the following two parameters:

  1. Requested Privilege: This represents the required privilege level for a resource, passed by a route, the CanAccess component, or other.
  2. User Privileges: These are the current privileges of the user, obtained from the authProvider.getUserPrivileges method.

The method returns a boolean value indicating whether the user is authorized to access the requested resource.

Key Notes
  • Execution Context: The canAccess method is only invoked when the user is authenticated via the current authentication method. If the user is not authenticated, canAccess check user is authenticated at first.
  • Public Resources: For resources that do not require authentication or authorization (e.g., a public Home page), do not define the requested privilege(auth) or set it to null.

The default implementation for the canAccess method is as follows. This logic can be customized to meet your application's specific requirements:

function defaultCanAccess(requestedPrivileges: RequestedPrivileges, userPrivileges: unknown) {
	/**
	 * Allow access if no privileges are required.
	 */
	if (requestedPrivileges === null || requestedPrivileges === undefined) {
		return true;
	}

	/**
	 * If the required privileges array is empty,
	 * allow access only if the user role is 'guest' (null or empty array).
	 */
	if (Array.isArray(requestedPrivileges) && requestedPrivileges.length === 0) {
		return !userPrivileges || (Array.isArray(userPrivileges) && userPrivileges.length === 0);
	}

	/**
	 * Check for matching privileges between requested and user privileges.
	 */
	if (userPrivileges && Array.isArray(userPrivileges)) {
		if (Array.isArray(requestedPrivileges)) {
			return requestedPrivileges.some((privilege) => userPrivileges.includes(privilege));
		}

		return userPrivileges.includes(requestedPrivileges);
	}

	if (Array.isArray(requestedPrivileges)) {
		return requestedPrivileges.includes(userPrivileges as string);
	}

	return requestedPrivileges === userPrivileges;
}
Example: Customizing the canAccess Logic

Below is an example of how to override the default canAccess logic for specific use cases:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter } from '@remate/core';

// Define privilege levels
export type Privileges = 'admin' | 'staff' | 'user';

// Define the user structure
export interface IUser {
	role: Privileges;
	email: string;
}

export const authProvider: IAuthProviderAdapter<IUser, Privileges> = {
	async getUserPrivileges(user) {
		// Return the user's role as their privilege
		return user.role;
	},
	// Customize the `canAccess` logic
	canAccess(requestedPrivilege, userPrivileges) {
		/**
		 * Example: Grant access if the requested privilege
		 * matches the user's privilege.
		 */
		return requestedPrivilege === userPrivileges;
	}
	// Additional methods like signIn, signUp, and signOut would go here.
};

updateUser

The updateUser method is an optional but highly useful feature that allows updating user information. For example, you can send a request to the server to update the user's data and return the updated user object. This method must return the updated user data conforming to the TUser type defined in the IAuthProviderAdapter interface.

parameters:

  1. userData: A PartialDeep<TUser> object containing the updated user fields.
  2. currentUser: The current state of the user object, which you can use if needed to calculate or validate updates.

Typically, this method is invoked using the updateUser function provided by the useAuth hook.

Example: Implementing updateUser

Below is an example of how to define and use the updateUser method in the authProvider:

Auth Provider Implementation
import React from 'react';
import { IAuthProviderAdapter } from '@remate/core';

export interface IUser {
	email: string;
	firstName: string;
	lastName: string;
}

export const authProvider: IAuthProviderAdapter<IUser> = {
	async updateUser(userData, currentUser) {
		/**
		 * Simplified example: Merge the new user data with the old user data.
		 * In a real-world scenario, send a request to the server and store the updated data.
		 */
		return { ...currentUser, ...userData };
	}
	// Other methods like signIn, signOut, and check would go here.
};
Example: Update Profile Component

The following is an example of a simple "Update Profile" page that uses the updateUser method from the useAuth hook:

Update Profile Page
import React from 'react';
import { useAuth } from '@remate/core';

export const UpdateProfilePage = () => {
	const { updateUser } = useAuth();

	const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
		e.preventDefault();

		// Extract form data
		const formData = Object.fromEntries(new FormData(e.currentTarget).entries());

		// Call the updateUser method
		updateUser(formData);

		// Reset the form
		e.currentTarget.reset();
	};

	return (
		<div>
			<h1>Update Profile</h1>
			<form onSubmit={onSubmit}>
				<input
					type="text"
					name="firstName"
					placeholder="First Name"
					required
				/>
				<input
					type="text"
					name="lastName"
					placeholder="Last Name"
					required
				/>
				<button type="submit">Submit</button>
			</form>
		</div>
	);
};

checkingAuthFallback

A fallback React node to display while authentication checks are in progress.

Example
import React from 'react';
import { IAuthProviderAdapter } from '@remate/core';

export const authProvider: IAuthProviderAdapter = {
	checkingAuthFallback: <h1>chcking auth...</h1>
	// Other methods like signIn, signOut, and check would go here.
};

routeAccessDeniedFallback

A fallback React node to display while user has not Route requested privileges.

if routeAccessDeniedFallback not provided, user will be redirect to loginRedirectUrl by default if has not access to the route.

Example
import React from 'react';
import { IAuthProviderAdapter } from '@remate/core';

export const authProvider: IAuthProviderAdapter = {
	routeAccessDeniedFallback: <h1>Access Denied</h1>
	// Other methods like signIn, signOut, and check would go here.
};

initialUserState

The initial state of user (before sign in). user state is null by default.

useAuth Hook

The useAuth hook provides all the necessary methods and information for handling authentication and authorization in your application. We've already seen examples of this hook in use; now, let’s delve into its type to better understand its capabilities.

Type Overview

Below is the type Overview for the useAuth hook, which is generic and can be tailored to your specific user (TUser) and privilege (TPrivileges) types:

type useAuth = <TUser = unknown, TUserPrivileges = unknown, TRequestedPrivileges = any>() => {
	isAuthenticated: boolean;
	user: TUser;
	setUser: (value: TUser | ((lastUserState: TUser) => TUser)) => void;
	resetUser: () => void;
	refreshUser: () => void | Promise<void>;
	userPrivileges: TUserPrivileges | null;
	signOut: <T extends unknown[]>(...args: T) => void | Promise<void>;
	signIn: <T extends unknown[]>(...args: T) => void | Promise<void>;
	signUp: <T extends unknown[]>(...args: T) => void | Promise<void>;
	updateUser: (userData: PartialDeep<TUser>) => void | Promise<void>;
	canAccess: (requestedPrivileges: TRequestedPrivileges) => boolean;
	checkError: (error: any) => void;
	isProvided: boolean;
};

Explanation of Methods and Properties

  1. isAuthenticated:
    A boolean value indicating whether the user is currently authenticated.

  2. user:
    The current user object, which is of type TUser. This holds all user-specific details.

  3. setUser:
    A method to update the user object.

    • Accepts either a new TUser object or a function that takes the current user state and returns the updated state.
  4. resetUser:
    A method to reset the user state to initial user state.

  5. refreshUser:
    A method to refresh the current user state by fetching the latest data (call the getUser method and set user state based on that).

  6. userPrivileges:
    The current privileges assigned to the user, of type TPrivileges.
    If the user is unauthenticated, this will be null.

  7. checkError:
    this method calls the onError method from the authProvider under the hood.

  8. signOut:
    Logs the user out. Can be asynchronous if additional cleanup or server communication is required.

  9. signIn:
    A generic method for signing in the user using additional arguments.

  10. signUp:
    Similar to signIn, this method is for registering a new user. Accepts an authentication method and arguments.

  11. isProvided:
    a boolean value that ditermines authProvider is provided or not

  12. updateUser:
    Updates the user's information using a partial object (PartialDeep<TUser>) containing the updated fields.

  13. canAccess:
    A method to determine if the user has the required privileges to access a resource.

    • Takes requestedPrivileges as input.
    • Returns a boolean.

Example Usage

Here’s a basic example of how to use the useAuth hook in a React component:

import React from 'react';
import { useAuth } from '@remate/core';

export const UserProfile = () => {
	const { user, isAuthenticated, signOut, updateUser, canAccess } = useAuth();

	const handleSignOut = () => {
		signOut();
	};

	const handleUpdateProfile = () => {
		const updatedUserData = { firstName: 'John', lastName: 'Doe' };
		updateUser(updatedUserData);
	};

	if (!isAuthenticated) {
		return <div>Please log in to access your profile.</div>;
	}

	return (
		<div>
			<h1>Welcome, {user.firstName}</h1>
			<button onClick={handleUpdateProfile}>Update Profile</button>
			<button onClick={handleSignOut}>Log Out</button>
			{canAccess('admin') && <div>You have admin access.</div>}
		</div>
	);
};

Router Integrations

To manage access control for your routes, for check authenticated you can use authenticated option in route configuration and for authorization you can define the requested privilege for each route. This is done by setting the desired privileges property on the route configuration. Behind the scenes, the privileges value is passed to the canAccess method. Based on the result:

  • If the user has the required access, the route will be displayed.
  • If not, the user will be redirected to the loginRedirectUrl from authProvider by default or display routeAccessDeniedFallback if provided.

Propagation to Child Routes

If a route has the privileges or authenticated property defined, the same privilege level will be applied to all its child routes, unless an privileges or authenticated property is explicitly defined for a specific child route.

Example: Setting Up Routes with Access Control

Below is an example illustrating how to configure route access control using the privileges property:

import { RemateRoutesType } from '@remate/core';

const routes: RemateRoutesType<string> = [
	// Route accessible only by authenticated user
	{
		path: '/',
		element: <h1>Home Page</h1>,
		authenticated: true
	},

	// Route accessible only by authenticated user and admin role
	{
		path: '/admin-dashboard',
		element: <h1>Admin Dashboard</h1>,
		privileges: 'admin' // Required privilege for this route
	},

	// Grouped routes for authentication pages (e.g., Sign-In and Sign-Up)
	{
		authenticated: false,
		layout: {
			style: 'raw' // Optional layout configuration
		},
		isNavigable: false, // Indicates these routes are not part of the main navigation
		children: [
			{
				path: '/sign-in',
				element: <h1>Sign In Page</h1> // Public sign-in page
			},
			{
				path: '/sign-up',
				element: <h1>Sign Up Page</h1> // Public sign-up page
			}
		]
	}
];

Authenticated Component

The Authenticated component is a powerful utility for conditionally rendering pages or components based on user authenticated status.

How It Works

  1. Authenticated Check:
    check user is authenticated
  2. Fallback Rendering:
    If the user does not authenticated, the content provided in the fallback prop is rendered instead.

Props

  • fallback (ReactNode | null):
    Content to render when the user is not authorized. Defaults to null.

Example: Basic Usage

Below is a simple example of using the Authenticated component to manage access control on a page:

import { Authenticated } from '@remate/core';

export const ListPage = () => {
	return (
		<Authenticated fallback={<button>sign in</button>}>
			<>
				<h1>User info</h1>
				...
			</>
		</Authenticated>
	);
};

CanAccess Component

The CanAccess component is a powerful utility for conditionally rendering pages or components based on user privileges. It integrates seamlessly with the canAccess method provided by the useAuth hook to determine whether a user has the necessary permissions.

How It Works

  1. Privilege Check:
    The privileges prop specifies the required privilege(s) for accessing the component.

  2. Fallback Rendering:
    If the user does not have the required privileges, the content provided in the fallback prop is rendered instead.

  3. Nested Access Control:
    The CanAccess component supports nesting, allowing granular control over different sections of your UI.

Props

  • privileges (TRequestedPrivileges = any):
    Specifies the required privilege(s). It can be a single string or an array of privileges.
  • fallback (ReactNode | null):
    Content to render when the user is not authorized. Defaults to null.

Example: Basic Usage

Below is a simple example of using the CanAccess component to manage access control on a page:

import { CanAccess } from '@remate/core';

export const ListPage = () => {
	return (
		<CanAccess
			privileges="user"
			fallback={<h1>You are not authorized to see this page.</h1>}
		>
			<>
				<h1>Products</h1>
				<CanAccess privileges="admin">
					<Button>See Details</Button>
				</CanAccess>
			</>
		</CanAccess>
	);
};

Explanation of the Example

  1. Outer CanAccess:

    • Ensures that only users with the user privilege can see the Products page.
    • If the user lacks user privileges, the fallback message ("You are not authorized to see this page.") is displayed.
  2. Nested CanAccess:

    • Within the Products page, access to the "See Details" button is restricted to users with the admin privilege.

Layouts

Remate features a powerful layout system that allows you to configure and use different layouts for each route. This flexibility makes it easy to have specialized pages, such as a login page without a toolbar or navbar, while keeping the rest of the application consistent.

To start using the layout system, you need to define your application's layouts and pass them to the Remate component as key-value pairs. Here’s how you can do it:

Defining Layouts

The following example demonstrates how to define layouts and pass them to the Remate component:

import { Remate, ILayoutProps } from '@remate/core';
import { BrowserRouter } from 'react-router';

function DefaultLayoutComponent({ children, config }: ILayoutProps) {
	// Custom configuration from route config
	if (config.raw) {
		return children;
	}

	return <main>{children}</main>;
}

const myLayouts = {
	default: ({ config: configFromRouteConfig, children }: ILayoutProps) => (
		<DefaultLayoutComponent config={configFromRouteConfig}>{children}</DefaultLayoutComponent>
	)

	// Additional layouts can be defined here...
};

function App() {
	return (
		<BrowserRouter>
			<Remate
				// Passing layouts to the system
				layouts={myLayouts}
				// Specifying the default layout
				// to use when no layout is defined in route config
				defaultLayoutStyle="default"
			>
				{/* Children components */}
			</Remate>
		</BrowserRouter>
	);
}

export default App;

Layout Configuration for Routes

Once you have defined and passed layouts to the system, you can configure layouts for specific routes as needed. This configuration is hierarchical, meaning a parent route’s layout configuration applies to its children unless overridden by a child’s layout configuration.

Layout Configuration Options

  1. Style: Specifies which layout component to render.
  2. Config: An object that passes custom data to the layout component. For example, you can send a flag to hide the toolbar or navbar on a specific page.

Example: Configuring Layouts for Routes

The following example demonstrates how to configure layouts for routes:

import { RemateRoutesType } from '@remate/core';

import Homepage from './pages/Home';
import SignInPage from './pages/SignIn';

const routes: RemateRoutesType = [
	{
		path: '/',
		element: <HomePage />,
		layout: {
			style: 'homeLayout'
		}
	},
	{
		path: '/signin',
		element: <SignInPage />,
		layout: {
			config: {
				raw: true
			}
		}
	}
];

Key Points

  • Default Layout: The defaultLayoutStyle property defines the layout to use if no layout is specified in the route configuration.
  • Hierarchical Configuration: Layout configurations are inherited by child routes unless explicitly overridden. When overridden, configurations are merged hierarchically.
  • Custom Configurations: The config object allows you to pass any data to the layout component, enabling highly customizable layouts.

Routing

Remate leverages a custom routing system built on top of the robust React Router v7. This system enables modular and flexible management of application routes, ensuring that your routing strategy is highly adaptable to various application requirements.

Key Features

  • Flexible Authorization: Easily enforce route-level permissions using the privileges property.
  • Dynamic Layouts: Customize layouts for individual routes or groups of routes using the layout configuration.
  • Dynamic Titles: Seamlessly manage document titles and breadcrumbs with the title and dynamicTitle properties.
  • Nested Routing: Define parent and child routes with cascading configurations for layouts and metadata.
  • Custom Metadata: Add meta properties to routes for extended functionality, such as analytics or custom behaviors.
  • Navigation Stack State: Access the current navigation state to build breadcrumbs or navigators dynamically.

This system ensures that you have full control over how routes are structured and managed within your Remate application.

Note: Authorization (privileges) and layout settings are hierarchically merged with child routes, but child route configurations take precedence over parent settings.

Defining Routes

To define the routes for your application, you provide an array of route objects to the <Remate /> component. These route objects use a custom type, RemateRouteItemType, which extends the base RouteObject type from React Router.

Route Object Structure

The RemateRouteItemType extends RouteObject by introducing several additional properties to enhance route configuration:

  • authenticated (Optional): Indicates whether the route requires the user to be authenticated or not.
  • privileges (Optional): Specifies the required privileges a user must have to access the route.
  • isNavigable (Optional): Indicates whether the route should appear in the navigation stack. Defaults to true.
  • title (Optional): Sets the title for the route, which is used for document titles and breadcrumbs.
  • id (Optional): A unique identifier for the route, useful for manipulating the navigation stack.
  • dynamicTitle (Optional): Enables dynamic title handling. If true, the loading indicator is shown until the title is resolved. Defaults to false.
  • layout (Optional): Defines layout configuration for the route.
  • meta (Optional): Provides metadata for the route.

Example: Defining Routes

Here’s an example of how to define application routes using RemateRouteItemType:

import { Navigate } from 'react-router';
import { authPrivileges, RequiredPrivilegesType } from '@auth';
import Error404Page from '@main/404/Error404Page';
import ExampleConfig from '@main/example/ExampleConfig';
import SignInConfig from '@main/sign-in/SignInConfig';
import SignUpConfig from '@main/sign-up/SignUpConfig';
import { RemateRoutesType } from '@remate/core';

/**
 * The routes of the application.
 */
const routes: RemateRoutesType<RequiredPrivilegesType> = [
	{
		path: '/',
		element: <Navigate to="/example" />,
		authenticated: true
	},

	{
		layout: {
			config: {
				navbar: { display: false },
				toolbar: { display: false },
				footer: { display: false },
				leftSidePanel: { display: false },
				rightSidePanel: { display: false }
			}
		},
		isNavigable: false,
		authenticated: false,
		children: [...SignInConfig, ...SignUpConfig]
	},

	...ExampleConfig,

	{
		path: '*',
		authenticated: true,
		title: 'PAGE_NOT_FOUND',
		isNavigable: false,
		element: <Error404Page />
	}
];

export default routes;

Integrating Routes with <Remate />

After defining your routes, pass them to the <Remate /> component within the application’s root component. Here’s an example:

import { BrowserRouter } from 'react-router';
import { Remate } from '@remate/core';

import routes from './routes';

function App() {
	return (
		<BrowserRouter>
			<Remate
				routes={routes}
				// Additional props can be passed here
			>
				{/* Children components */}
			</Remate>
		</BrowserRouter>
	);
}

export default App;

Routing Hooks

Remate provides a set of powerful routing hooks that can be used in pages, layouts, or any child components of the <Remate /> component. These hooks offer various routing-related capabilities, enabling developers to manage navigation and state efficiently.

Below is an explanation of these hooks, with examples for better understanding.

Example Configuration for Reference

Here is an example route configuration to provide context for the following hook explanations:

import { lazy } from 'react';
import { authPrivileges } from '@auth';
import { RemateRoutesType } from '@remate/core';

const Example = lazy(() => import('./Example'));
const ExampleDetailsPage = lazy(() => import('./ExampleDetailsPage'));
const ExampleNestedPage = lazy(() => import('./ExampleNestedPage'));

/**
 * The Example page config.
 */
const ExampleConfig: RemateRoutesType = [
	{
		privileges: authPrivileges.user,
		path: 'example',
		title: 'example page',
		id: 'example-main',
		children: [
			{
				index: true,
				element: <Example />
			},
			{
				path: ':id',
				dynamicTitle: true,
				id: 'example-details',
				children: [
					{
						index: true,
						privileges: authPrivileges.staff,
						element: <ExampleDetailsPage />
					},
					{
						path: 'nested',
						element: <ExampleNestedPage />,
						title: 'EXAMPLE_NESTED',
						privileges: authPrivileges.admin,
						id: 'example-nested'
					}
				]
			}
		]
	}
];

export default ExampleConfig;

useMatched

Returns all routes that match the current pathname. If the current route has a parent route, the parent is also included, providing a hierarchical path from the root to the current route.

Return Type: An array of type RemateRouteMatchType.

Example Usage: Suppose the current pathname is /example/1/nested.

import { useMatched } from '@remate/core';

export default function ExampleNestedPage() {
	const matchedRoutes = useMatched();
	console.log(matchedRoutes); // Outputs the matched routes hierarchy
	return 'Example Nested Page';
}

useRoutes

Returns all defined routes in the application.

Return Type: An array of type RemateRouteItemType.

useNavigationStackState

Provides the current navigation stack state, which includes only navigable routes. Routes with index: true, path: '', or isNavigable: false are excluded.

Return Type: An array of type INavigationStackStateItem.

Use Case: Ideal for building breadcrumbs, navigator buttons (e.g., Back), logging, or analytics.

Example Usage: Suppose the current pathname is /example/1/nested.

import { useNavigationStackState } from '@remate/core';

export default function ExampleNestedPage() {
	const navigationStackState = useNavigationStackState();
	console.log(navigationStackState); // Outputs the current navigation stack
	return 'Example Nested Page';
}

useCurrentNavigationStackStateItem

Returns the last item in the navigation stack, representing the current route.

Example Usage: To display the title of the current page in the toolbar:

export default function Toolbar() {
	const currentNavigationStackItem = useCurrentNavigationStackStateItem();
	return (
		<div>
			<h1>{currentNavigationStackItem?.title}</h1>
		</div>
	);
}

useNavigationStackStateItemById

Allows selecting a specific item from the navigation stack state by its id. If the item does not exist, it returns null.

Input:

  • id (string): The ID of the route to retrieve.

Return Type: INavigationStackStateItem | null

Example:

import { useNavigationStackStateItemById } from '@remate/core';

export default function ExamplePage() {
	const stackItem = useNavigationStackStateItemById('example-details');
	console.log(stackItem);
	return 'Example Page';
}

useSetDynamicTitle

Used for routes with dynamic titles, typically when the title is determined after fetching data from an API. This hook updates the title in the navigation stack state automatically.

Example Usage:

import { useEffect, useState } from 'react';
import { useNavigationStackStateItemById, useSetDynamicTitle } from '@remate/core';

function ExampleDetailsPage() {
	const { id } = useParams();
	const [dynamicTitle, setDynamicTitle] = useState<string | null>(null);
	const currentStackItem = useNavigationStackStateItemById('example-details');

	// Simulate fetching data from an API
	useEffect(() => {
		setTimeout(() => {
			setDynamicTitle(`Example details ${id} dynamic title`);
		}, 1000);
	}, [id]);

	useSetDynamicTitle('example-details', dynamicTitle);

	return <div className="p-24">{currentStackItem?.title ? <h4>{currentStackItem.title}</h4> : 'loading'}</div>;
}

export default ExampleDetailsPage;

Routing Actions

In this section, we explore methods that allow you to perform changes on the navigation state and routes in your application. These actions provide dynamic control over routing behaviors, enabling better adaptability to complex navigation scenarios.

resetNavigationStackState

The resetNavigationStackState action resets the navigation stack state.

  • Parameters:
    • routeId (optional): If provided, only the specified stack item will be reset.

This method is useful when you want to clear navigation states for a route or the entire stack.

Example Usage
import { useEffect, useState } from 'react';
import { useNavigationStackStateItemById, useSetDynamicTitle, resetNavigationStackState } from '@remate/core';

function ExampleDetailsPage() {
	const { id } = useParams();
	const [dynamicTitle, setDynamicTitle] = useState<string | null>(null);
	const currentStackItem = useNavigationStackStateItemById('example-details');

	// Simulate fetching data from an API
	useEffect(() => {
		setTimeout(() => {
			setDynamicTitle(`Example details ${id} dynamic title`);
		}, 1000);
	}, [id]);

	useSetDynamicTitle('example-details', dynamicTitle);

	return (
		<div className="p-24">
			{currentStackItem?.title ? (
				<>
					<h4>{currentStackItem.title}</h4>
					<button
						onClick={() => {
							resetNavigationStackState();
							// Reset navigation state for a specific route
							// resetNavigationStackState('example-details');
						}}
					>
						Reset Navigation State
					</button>
				</>
			) : (
				'Loading...'
			)}
		</div>
	);
}

export default ExampleDetailsPage;

setNavigationStackStateItemSetting

The setNavigationStackStateItemSetting action allows you to modify the settings of a navigation stack state item.

  • Key Features:
    • Can be used both before and after a stack item exists in the navigation state.
    • Overrides default settings of the stack state item.
Example Usage
import { useNavigationStackStateItemById, setNavigationStackStateItemSetting } from '@remate/core';

function ExampleDetailsPage() {
	const currentStackItem = useNavigationStackStateItemById('example-details');

	return (
		<div className="p-24">
			<h4>{currentStackItem?.title || 'Loading...'}</h4>

			<button
				onClick={() => {
					setNavigationStackStateItemSetting('example-details', {
						title: 'New Route Title'
						// Additional settings can be modified here
					});
				}}
			>
				Set Navigation State
			</button>
		</div>
	);
}

export default ExampleDetailsPage;

Notifications

Notifications and visual feedback play a crucial role in enhancing user experience within an application. Remate provides a notification integration system that operates seamlessly in scenarios such as failed requests, successful form submissions, or other key events.

Notification Provider

Remate allows you to configure a custom notification API by passing a notificationProvider to the <Remate /> component.

The notificationProvider is an object that includes two essential methods:

  • open: Displays a notification with specified parameters.
  • close: Hides a notification identified by its key.

These methods can be accessed and invoked from anywhere in the application using the useNotification hook, ensuring flexibility and ease of integration.

Structure of notificationProvider

A notificationProvider must implement the following methods:

const notificationProvider = {
	open: (params: OpenNotificationParams) => {},
	close: (key: string) => {}
};

The associated type definitions for these methods are:

interface NotificationProvider {
	open: (params: OpenNotificationParams) => void;
	close: (key: string) => void;
}

interface OpenNotificationParams {
	key?: string;
	type: 'success' | 'error' | 'warning' | 'info';
	message: string;
	description?: string;
}

Integrating notificationProvider

To enable notifications in Remate, pass your custom notificationProvider object to the <Remate /> component.

Below is an example using react-toastify as the notification provider:

import { toast } from 'react-toastify';
import { Remate, NotificationProviderType } from '@remate/core';

const notificationProvider: NotificationProviderType = {
	open: ({ key, message, type }) => {
		if (toast.isActive(key as string)) {
			toast.update(key as string, {
				render: message,
				closeButton: true,
				autoClose: 5000,
				type
			});
		} else {
			toast(message, {
				toastId: key,
				type
			});
		}
	},
	close: (key) => toast.dismiss(key)
};

const App = () => {
	return (
		<Remate
			notificationProvider={notificationProvider}
			/* ...other props */
		>
			{/* App components */}
		</Remate>
	);
};

useNotification Hook

The useNotification hook provides access to the open and close methods from the configured notificationProvider. This hook allows you to trigger notifications dynamically from any part of your application.

Usage Example

Below is an example demonstrating how to use the useNotification hook:

import { useNotification } from '@remate/core';

const NotificationExample = () => {
	const { open, close } = useNotification();

	// Trigger a success notification
	const handleOpen = () => {
		open({
			key: 'notification-key',
			type: 'success',
			message: 'Success',
			description: 'This is a success message'
		});
	};

	// Dismiss a notification
	const handleClose = () => {
		close('notification-key');
	};

	return (
		<div>
			<button onClick={handleOpen}>Show Notification</button>
			<button onClick={handleClose}>Hide Notification</button>
		</div>
	);
};

export default NotificationExample;

Data Fetching

Data fetching is a crucial component of any UI application, enabling seamless interaction between users and underlying data sources. UI applications act as intermediaries, allowing users to interact with and manipulate data in meaningful ways.

Overview

Remate leverages @tanstack/react-query v5 under the hood to handle data fetching. It introduces a unique syntax for defining endpoints, enabling reusability and separation of concerns. This system supports diverse data-fetching mechanisms, such as RESTful APIs, with remarkable flexibility and scalability.

Key Features

  • Integration with React Query: Provides built-in caching and data fetching mechanisms based on React Query.
  • UI Layer Agnostic: The functionality can be used with any UI layer.
  • Predefined API Endpoints: Endpoints are defined ahead of time, specifying query parameters and response transformations for caching.
  • React Hook Generation: Automatically generates React hooks to encapsulate data-fetching logic, including data and isLoading states.
  • Error Handling: Supports UI error notifications and retry mechanisms using error boundaries.
  • TypeScript Support: Fully written in TypeScript for an excellent development experience.
  • Extensibility: Highly customizable and extensible while leveraging all features of React Query.
  • Reusable Hooks: Generates reusable hooks for each endpoint, promoting separation of concerns by decoupling data-fetching logic from React components.

Getting Started

Using Remate’s data-fetching system is straightforward:

  1. Define your API service with custom logic and types using the createApi method.
  2. Use the buildEndpoints method of created apiService with createApi to define endpoints such as query, mutation, and infiniteQuery.
  3. Leverage the generated hooks in your UI components.

Example Usage

Wrapping the Application with QueryClientProvider

To start, wrap your application with the QueryClientProvider from @tanstack/react-query and include the Remate component:

import { Remate } from '@remate/core';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

const App = () => {
	return (
		<QueryClientProvider client={queryClient}>
			<Remate>{/* Application Components */}</Remate>
		</QueryClientProvider>
	);
};
Defining Base Query Logic

You can define the base query logic like using Axios:

import { BaseQueryFn, createApi } from '@remate/core';
import axios, { AxiosError, AxiosRequestConfig } from 'axios';

const axiosBaseQuery =
	(): BaseQueryFn<string | AxiosRequestConfig, unknown, AxiosError> =>
	async (args, { signal }) => {
		try {
			const result = await axios({ signal, ...(typeof args === 'string' ? { url: args } : args) });
			return { data: result.data };
		} catch (error) {
			return { error: error as AxiosError };
		}
	};

export const apiService = createApi({
	baseQuery: axiosBaseQuery()
});
Creating Endpoints

Define endpoints using the buildEndpoints method of your apiService and generate hooks for use in components:

const { useCreateUserMutation, useGetInfiniteUsersInfiniteQuery, useGetUsersQuery } = apiServi