Skip to content

Commit 99a6cc4

Browse files
committed
feat: add support for multiple mounted instances of ConfirmComponentHost (#4)
1 parent 7f97497 commit 99a6cc4

File tree

4 files changed

+104
-55
lines changed

4 files changed

+104
-55
lines changed

README.md

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { ConfirmComponentHost } from "react-confirm-service";
2424
<Notification
2525
isOpen={props.isVisible}
2626
onClose={props.onClose}
27-
...
2827
>
2928
{props.message}
3029
</Notification>
@@ -34,24 +33,33 @@ import { ConfirmComponentHost } from "react-confirm-service";
3433
isOpen={props.isOpen}
3534
title={props.title}
3635
>
37-
// render content, buttons, ...
36+
{/* render content, buttons, ... */}
3837
</Dialog>
3938
)}
4039
renderChoice={props => {
41-
const options = props.options.map(option => <li key={option.key} onClick={() => props.onConfirm(option)}>{option.key}</li>);
42-
43-
<Dialog
44-
isOpen={props.isOpen}
45-
title={props.title}
46-
>
47-
<>
48-
<ul>
49-
{options}
50-
</ul>
51-
<button onClick={props.onCancel}>Cancel</button>
52-
</>
53-
</Dialog>
54-
)}
40+
const options = props.options.map(option => (
41+
<li
42+
key={option.key}
43+
onClick={() => props.onConfirm(option)}
44+
>
45+
{option.key}
46+
</li>
47+
));
48+
49+
return (
50+
<Dialog
51+
isOpen={props.isOpen}
52+
title={props.title}
53+
>
54+
<>
55+
<ul>
56+
{options}
57+
</ul>
58+
<button onClick={props.onCancel}>Cancel</button>
59+
</>
60+
</Dialog>
61+
);
62+
}}
5563
/>
5664
</MyApp>
5765
~~~
@@ -101,6 +109,8 @@ The `ConfirmComponentHost` accepts the following props:
101109
| strings | no | Takes an object to provide default values for `yes`, `no`, and `cancel` button captions. Use this to localize these texts. |
102110
| alertDurations | no | You can provide an object to set the durations of an alert for each severity in ms. The defaults are: info: 3000, success: 3000, warning: 10000, error: 10000. |
103111

112+
You can use the `ConfirmComponentHost` in multiple places in your application. The `ConfirmService` will use the last one which was mounted.
113+
104114
### renderAlert
105115

106116
`renderAlert` is a function with one parameter of type `AlertRenderProps`:

src/Component.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ const defaultDurations: AlertDurations = {
7676
};
7777

7878
class ConfirmComponentHost extends React.Component<Props, State> {
79+
private readonly alert: Service.Handlers;
80+
7981
public constructor (props: Props) {
8082
super(props);
8183

@@ -106,15 +108,20 @@ class ConfirmComponentHost extends React.Component<Props, State> {
106108
},
107109
},
108110
};
111+
112+
this.alert = {
113+
alert: this.showAlert,
114+
confirm: this.showConfirm,
115+
choose: this.showChoice,
116+
};
109117
}
110118

111119
public override componentDidMount (): void {
112-
Service.initAlerts(this.showAlert, this.showConfirm, this.showChoice);
120+
Service.addHandlers(this.alert);
113121
}
114122

115-
// eslint-disable-next-line class-methods-use-this
116123
public override componentWillUnmount (): void {
117-
Service.initAlerts(null, null, null);
124+
Service.removeHandlers(this.alert);
118125
}
119126

120127
public override render (): React.ReactNode {

src/Service.ts

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,29 @@ interface ChooseOptions<TData extends Option = Option> {
5151
cancelCaption?: string,
5252
}
5353

54-
let globalAlert: AlertFunc | null;
55-
let globalConfirm: ConfirmFunc | null;
56-
let globalChoose: ChooseFunc | null;
57-
58-
function initAlerts (alert: AlertFunc | null, confirm: ConfirmFunc | null, choose: ChooseFunc | null): void {
59-
globalAlert = alert;
60-
globalConfirm = confirm;
61-
globalChoose = choose;
54+
interface Handlers {
55+
alert: AlertFunc,
56+
confirm: ConfirmFunc,
57+
choose: ChooseFunc,
58+
}
59+
60+
const globalHandlers: Handlers [] = [];
61+
62+
function addHandlers (handlers: Handlers): void {
63+
removeHandlers(handlers);
64+
globalHandlers.push(handlers);
65+
}
66+
67+
function removeHandlers (handlers: Handlers): void {
68+
const index = globalHandlers.indexOf(handlers);
69+
70+
if (index >= 0) {
71+
globalHandlers.splice(index, 1);
72+
}
73+
}
74+
75+
function getCurrentHandlers (): Handlers | undefined {
76+
return globalHandlers.at(-1);
6277
}
6378

6479
const ConfirmService = {
@@ -68,29 +83,35 @@ const ConfirmService = {
6883
* @param severity The severity of the alert.
6984
*/
7085
alert (this: void, message: string, severity: AlertSeverity): void {
71-
if (globalAlert) {
72-
globalAlert(message, severity);
73-
} else {
86+
const handlers = getCurrentHandlers();
87+
88+
if (!handlers) {
7489
throw new Error("ConfirmService is not initialized.");
7590
}
91+
92+
handlers.alert(message, severity);
7693
},
7794
/**
7895
* Shows a confirmation.
7996
* @param options The options for the confirmation.
8097
*/
8198
async confirm (this: void, options: ConfirmOptions): Promise<void> {
8299
return new Promise((resolve, reject) => {
83-
if (globalConfirm) {
84-
globalConfirm(options, result => {
85-
if (result) {
86-
resolve();
87-
} else {
88-
reject(new Error("Canceled"));
89-
}
90-
});
91-
} else {
100+
const handlers = getCurrentHandlers();
101+
102+
if (!handlers) {
92103
reject(new Error("ConfirmService is not initialized."));
104+
105+
return;
93106
}
107+
108+
handlers.confirm(options, result => {
109+
if (result) {
110+
resolve();
111+
} else {
112+
reject(new Error("Canceled"));
113+
}
114+
});
94115
});
95116
},
96117

@@ -101,29 +122,35 @@ const ConfirmService = {
101122
*/
102123
async choose<TData extends Option> (this: void, options: ChooseOptions<TData>): Promise<TData> {
103124
return new Promise<TData>((resolve, reject) => {
104-
if (globalChoose) {
105-
globalChoose(options as ChooseOptions, result => {
106-
if (result) {
107-
resolve(result as TData);
108-
} else {
109-
reject(new Error("Canceled"));
110-
}
111-
});
112-
} else {
125+
const handlers = getCurrentHandlers();
126+
127+
if (!handlers) {
113128
reject(new Error("ConfirmService is not initialized."));
129+
130+
return;
114131
}
132+
133+
handlers.choose(options as ChooseOptions, result => {
134+
if (result) {
135+
resolve(result as TData);
136+
} else {
137+
reject(new Error("Canceled"));
138+
}
139+
});
115140
});
116141
},
117142
};
118143

119144
export type {
120145
AlertSeverity,
146+
Handlers,
121147
ConfirmOptions,
122148
Option,
123149
ChooseOptions,
124150
};
125151

126152
export {
127-
initAlerts,
153+
addHandlers,
154+
removeHandlers,
128155
ConfirmService,
129156
};

tests/Service.spec.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import { ChooseOptions, ConfirmOptions, ConfirmService, initAlerts, Option } from "../src/Service";
1+
import { addHandlers, ChooseOptions, ConfirmOptions, ConfirmService, Handlers, Option, removeHandlers } from "../src/Service";
22

33
describe("Service", () => {
44
const mockAlert = jest.fn();
55
const mockConfirm = jest.fn();
66
const mockChoose = jest.fn();
7+
const alert: Handlers = {
8+
alert: mockAlert,
9+
confirm: mockConfirm,
10+
choose: mockChoose,
11+
};
712

813
beforeEach(() => {
914
mockAlert.mockReset();
1015
mockConfirm.mockReset();
1116
mockChoose.mockReset();
1217

13-
initAlerts(mockAlert, mockConfirm, mockChoose);
18+
addHandlers(alert);
1419
});
1520

1621
describe("alert", () => {
@@ -22,13 +27,13 @@ describe("Service", () => {
2227
ConfirmService.alert(message, severity);
2328

2429
// assert
25-
expect(mockAlert).toHaveBeenCalledTimes(1);
30+
expect(alert.alert).toHaveBeenCalledTimes(1);
2631
expect(mockAlert).toHaveBeenCalledWith(message, severity);
2732
});
2833

2934
it("throws if not initialized", () => {
3035
// arrange
31-
initAlerts(null, mockConfirm, mockChoose);
36+
removeHandlers(alert);
3237

3338
// act
3439
const fails = (): void => ConfirmService.alert("Message", "info");
@@ -82,7 +87,7 @@ describe("Service", () => {
8287

8388
it("throws if not initialized", async () => {
8489
// arrange
85-
initAlerts(mockAlert, null, mockChoose);
90+
removeHandlers(alert);
8691

8792
// act
8893
const fails = async (): Promise<void> => ConfirmService.confirm({
@@ -149,7 +154,7 @@ describe("Service", () => {
149154

150155
it("throws if not initialized", async () => {
151156
// arrange
152-
initAlerts(mockAlert, mockConfirm, null);
157+
removeHandlers(alert);
153158

154159
// act
155160
const fails = async (): Promise<Option> => ConfirmService.choose({

0 commit comments

Comments
 (0)