Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/custom-layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,59 @@ Toast.show({
```

All the available props on `BaseToast`, `SuccessToast`, `ErrorToast` or `InfoToast` components can be found here: [BaseToastProps](../src/types/index.ts#L86-L103).

## Custom toast types with TypeScript

When you create a custom toast type as in the example above, you can make it fully type-safe by utilizing Typescript declaration merging.

Example:

```tsx
import Toast, { ToastConfig } from 'react-native-toast-message';

interface TomatoToastParams {
uuid: string;
// ...
}

declare module 'react-native-toast-message' {
export interface CustomToastParamTypes {
tomatoToast: TomatoToastParams;
}
}
```

Now the `props` parameter will be correctly typed according to the toast type.

```tsx
const toastConfig: ToastConfig = {
tomatoToast: ({ text1, props }) => (
<View style={{ height: 60, width: '100%', backgroundColor: 'tomato' }}>
<Text>{text1}</Text>
{/* `props` will be of type `TomatoToastParams` here */}
<Text>{props.uuid}</Text>
</View>
)
};
```
Note that if you specify a custom toast type, then the config prop on the Toast component will become required:

```tsx
export function App(props) {
return (
<>
{...}
<Toast /> {/* Property 'config' is missing in type '{}' but required in type '{ config: ToastConfig; }'.ts(2741) */}
</>
);
}
```

Then you can use the new toast type with the correct typing:
```ts
Toast.show({
type: 'tomatoToast',
// if you mess something up in the props, typescript will scream at you properly
props: { uuid: 'bba1a7d0-6ab2-4a0a-a76e-ebbe05ae6d70' }
});
```
5 changes: 3 additions & 2 deletions src/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
ToastHideParams,
ToastProps,
ToastRef,
ToastShowParams
ToastShowParams,
ToastType
} from './types';
import { useToast } from './useToast';

Expand Down Expand Up @@ -121,7 +122,7 @@ function getRef() {
return activeRef.current;
}

Toast.show = (params: ToastShowParams) => {
Toast.show = <T extends ToastType = ToastType>(params: ToastShowParams<T>) => {
getRef()?.show(params);
};

Expand Down
87 changes: 68 additions & 19 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import {

export type ReactChildren = React.ReactNode;

export type ToastType = 'success' | 'error' | 'info' | (string & {});
export type ToastPosition = 'top' | 'bottom';

export type ToastOptions = {
export type ToastOptions<T extends ToastType = ToastType> = {
/**
* Toast type.
* Default value: `success`
*/
type?: ToastType;
type?: T;
/**
* Style for the header text in the Toast (text1).
*/
Expand Down Expand Up @@ -85,20 +84,41 @@ export type ToastOptions = {
* Called on Toast press
*/
onPress?: () => void;
/**
* Any custom props passed to the specified Toast type.
* Has effect only when there is a custom Toast type (configured via the `config` prop
* on the Toast instance) that uses the `props` parameter
*/
props?: any;
};
} & (keyof CustomToastParamTypes extends never
? { props?: any }
: T extends keyof CustomToastParamTypes
? Required<CustomToastParamTypes>[T] extends never
? { props?: any }
: undefined extends CustomToastParamTypes[T]
? {
/**
* Any custom props passed to the specified Toast type.
* Has effect only when there is a custom Toast type (configured via the `config` prop
* on the Toast instance) that uses the `props` parameter
*/
props?: CustomToastParamTypes[T];
}
: {
/**
* Any custom props passed to the specified Toast type.
* Has effect only when there is a custom Toast type (configured via the `config` prop
* on the Toast instance) that uses the `props` parameter
*/
props: CustomToastParamTypes[T];
}
: { props?: any });

export type ToastData = {
text1?: string;
text2?: string;
};

export type ToastShowParams = ToastData & ToastOptions;
export type ToastShow = <T extends ToastType = ToastType>(
params: ToastShowParams<T>
) => void;

export type ToastShowParams<T extends ToastType = ToastType> = ToastData &
ToastOptions<T>;

export type ToastHideParams = void;

Expand Down Expand Up @@ -135,24 +155,53 @@ export type ToastConfigParams<Props> = {
props: Props;
};

/**
* This interface is here to allow for types customization via declaration merging when using custom toast types.
* See [the docs](https://github.com/calintamas/react-native-toast-message/blob/main/docs/custom-layouts.md) for usage examples.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomToastParamTypes {}

export interface BuiltinToastParamsTypes {
success?: never;
error?: never;
info?: never;
}

export type ToastType = keyof (BuiltinToastParamsTypes & CustomToastParamTypes);

export type ToastConfig = {
[key: string]: (params: ToastConfigParams<any>) => React.ReactNode;
};
[key in keyof BuiltinToastParamsTypes]?: (
params: ToastConfigParams<BuiltinToastParamsTypes[key]>
) => React.ReactNode;
} & {
[key in keyof CustomToastParamTypes]-?: (
params: ToastConfigParams<CustomToastParamTypes[key]>
) => React.ReactNode;
} & Record<string, (params: ToastConfigParams<any>) => React.ReactNode>;

export type ToastRef = {
show: (params: ToastShowParams) => void;
show: ToastShow;
hide: (params: ToastHideParams) => void;
};

/**
* `props` that can be set on the Toast instance.
* They act as defaults for all Toasts that are shown.
*/
export type ToastProps = {
/**
* Layout configuration for custom Toast types
*/
config?: ToastConfig;
export type ToastProps = (keyof CustomToastParamTypes extends never
? {
/**
* Layout configuration for custom Toast types
*/
config?: ToastConfig;
}
: {
/**
* Layout configuration for custom Toast types
*/
config: ToastConfig;
}) & {
/**
* Toast type.
* Default value: `success`
Expand Down