Common Component Props
WithLoading Types
Types for loading state management in components.
type WithLoading = <T>(
promise: undefined | Promise<T | void> | (() => Promise<T | void>)
) => Promise<T | void>
type WithLoadingByKey = <T>(
key: string,
promise: undefined | Promise<T | void> | (() => Promise<T | void>)
) => Promise<T | void>
type LoadingByKey = { [key: string]: boolean }
import type { WithLoading } from '@proton/hooks';
import { useLoading } from '@proton/hooks';
interface ButtonProps {
onClick: () => Promise<void>;
children: React.ReactNode;
}
function AsyncButton({ onClick, children }: ButtonProps) {
const [loading, withLoading] = useLoading();
const handleClick = () => withLoading(onClick());
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Loading...' : children}
</button>
);
}
Ref Types
Combined Refs
Types for combining multiple React refs.
import type { Ref } from 'react';
type CombinedRef<T> = Ref<T>
import { forwardRef, useRef } from 'react';
import useCombinedRefs from '@proton/hooks/useCombinedRefs';
interface InputProps {
value: string;
onChange: (value: string) => void;
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ value, onChange }, ref) => {
const internalRef = useRef<HTMLInputElement>(null);
const combinedRef = useCombinedRefs(ref, internalRef);
return (
<input
ref={combinedRef}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
);
Controlled Component Types
Controlled Props Pattern
Types for controlled/uncontrolled component patterns.
interface ControlledProps<T> {
value?: T;
defaultValue?: T;
onChange?: (value: T) => void;
}
import useControlled from '@proton/hooks/useControlled';
interface TextInputProps extends ControlledProps<string> {
placeholder?: string;
disabled?: boolean;
}
function TextInput({
value,
defaultValue = '',
onChange,
placeholder,
disabled
}: TextInputProps) {
const [internalValue, setInternalValue] = useControlled(
value,
defaultValue
);
const handleChange = (newValue: string) => {
setInternalValue(newValue);
onChange?.(newValue);
};
return (
<input
value={internalValue}
onChange={(e) => handleChange(e.target.value)}
placeholder={placeholder}
disabled={disabled}
/>
);
}
API Component Types
Query Function Type
Type for API query functions used with useApiResult.
type QueryFunction = (...args: any[]) => {
method: string;
url: string;
data?: any;
params?: any;
}
import type { QueryFunction } from '@proton/components';
const queryUser: QueryFunction = (userId: string) => ({
method: 'get',
url: `/users/${userId}`
});
const updateUser: QueryFunction = (userId: string, data: any) => ({
method: 'put',
url: `/users/${userId}`,
data
});
API Result Type
Return type from useApiResult hook.
interface Result<R, U extends any[]> {
result: R | undefined;
error: Error;
loading: boolean;
request: (...args: U) => Promise<R>;
}
The fetched data (undefined while loading or on error)
Error object if request failed
Whether the request is in progress
request
(...args: U) => Promise<R>
Function to manually trigger the request
import type { Result } from '@proton/components';
import { useApiResult } from '@proton/components';
function DataComponent() {
const apiResult: Result<User[], []> = useApiResult(
queryUsers,
[]
);
const { result: users, loading, error, request } = apiResult;
return (
<div>
{loading && <Spinner />}
{error && <ErrorMessage error={error} />}
{users?.map(user => <UserCard key={user.ID} user={user} />)}
<button onClick={() => request()}>Refresh</button>
</div>
);
}
Cache Types
Cache Interface
Interface for the application cache.
interface Cache<K = string, V = any> {
get(key: K): V | undefined;
set(key: K, value: V): void;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
subscribe(callback: (key: K, value: V) => void): () => void;
}
import { useCache } from '@proton/components';
import type { Cache } from '@proton/shared/lib/helpers/cache';
interface User {
id: string;
name: string;
}
function UserManager() {
const cache: Cache<string, User> = useCache();
const saveUser = (user: User) => {
cache.set(`user-${user.id}`, user);
};
const getUser = (id: string): User | undefined => {
return cache.get(`user-${id}`);
};
return <div>...</div>;
}
Event Handler Types
Common Event Handlers
Typescript types for common React event handlers.
import type { MouseEvent, ChangeEvent, FormEvent } from 'react';
type ClickHandler = (event: MouseEvent<HTMLElement>) => void;
type ChangeHandler = (event: ChangeEvent<HTMLInputElement>) => void;
type SubmitHandler = (event: FormEvent<HTMLFormElement>) => void;
interface ButtonProps {
onClick: ClickHandler;
children: React.ReactNode;
}
function Button({ onClick, children }: ButtonProps) {
return <button onClick={onClick}>{children}</button>;
}
Polymorphic Component Types
Types for components that can render as different HTML elements.
import type { ComponentPropsWithoutRef, ElementType } from 'react';
type PolymorphicProps<E extends ElementType> = {
as?: E;
} & ComponentPropsWithoutRef<E>;
import type { ElementType } from 'react';
interface BoxProps<E extends ElementType = 'div'> {
as?: E;
children: React.ReactNode;
}
function Box<E extends ElementType = 'div'>({
as,
children,
...props
}: BoxProps<E> & Omit<ComponentPropsWithoutRef<E>, keyof BoxProps<E>>) {
const Component = as || 'div';
return <Component {...props}>{children}</Component>;
}
// Usage
<Box as="section">Content</Box>
<Box as="article">Article</Box>
<Box>Div by default</Box>
Best Practices
Use Generic Types
// Good - reusable with any type
function DataList<T>({ items }: { items: T[] }) {
return <ul>{items.map((item, i) => <li key={i}>{item}</li>)}</ul>;
}
// Usage
<DataList<User> items={users} />
<DataList<Product> items={products} />
Type Props Explicitly
// Good
interface UserCardProps {
user: User;
onEdit: (user: User) => void;
loading?: boolean;
}
function UserCard({ user, onEdit, loading = false }: UserCardProps) {
// ...
}
// Avoid
function UserCard(props: any) {
// ...
}
Use Discriminated Unions for Variants
type ButtonProps =
| { variant: 'link'; href: string; onClick?: never }
| { variant: 'button'; onClick: () => void; href?: never };
function Button(props: ButtonProps) {
if (props.variant === 'link') {
return <a href={props.href}>Link</a>;
}
return <button onClick={props.onClick}>Button</button>;
}